zhentao 1 month ago
parent
commit
9f18e069c5
64 changed files with 4946 additions and 3 deletions
  1. 1 0
      ArcFace64.dat
  2. BIN
      libs/WIN64/libarcsoft_face.dll
  3. BIN
      libs/WIN64/libarcsoft_face_engine.dll
  4. BIN
      libs/WIN64/libarcsoft_face_engine_jni.dll
  5. BIN
      libs/arcsoft-sdk-face-3.0.0.0.jar
  6. 9 1
      pom.xml
  7. 1 0
      src/main/java/com/zhentao/ZyzApplication.java
  8. 104 0
      src/main/java/com/zhentao/baoming/controller/AreaController.java
  9. 453 0
      src/main/java/com/zhentao/baoming/controller/OrderActivityController.java
  10. 135 0
      src/main/java/com/zhentao/baoming/controller/OrderSignupController.java
  11. 60 0
      src/main/java/com/zhentao/baoming/controller/OrderTagController.java
  12. 71 0
      src/main/java/com/zhentao/baoming/controller/ServiceCategoryController.java
  13. 103 0
      src/main/java/com/zhentao/baoming/dto/common/PageResult.java
  14. 286 0
      src/main/java/com/zhentao/baoming/dto/order/OrderActivityDTO.java
  15. 423 0
      src/main/java/com/zhentao/baoming/dto/order/OrderActivityDetailDTO.java
  16. 115 0
      src/main/java/com/zhentao/baoming/dto/order/OrderActivityQueryDTO.java
  17. 183 0
      src/main/java/com/zhentao/baoming/dto/order/OrderSignupDTO.java
  18. 18 0
      src/main/java/com/zhentao/baoming/mapper/ActivityTagRelationMapper.java
  19. 18 0
      src/main/java/com/zhentao/baoming/mapper/AreaMapper.java
  20. 18 0
      src/main/java/com/zhentao/baoming/mapper/EvaluationMapper.java
  21. 28 0
      src/main/java/com/zhentao/baoming/mapper/OrderActivityMapper.java
  22. 18 0
      src/main/java/com/zhentao/baoming/mapper/OrderSignupMapper.java
  23. 18 0
      src/main/java/com/zhentao/baoming/mapper/OrderTagMapper.java
  24. 18 0
      src/main/java/com/zhentao/baoming/mapper/ServiceCategoryMapper.java
  25. 77 0
      src/main/java/com/zhentao/baoming/pojo/ActivityTagRelation.java
  26. 92 0
      src/main/java/com/zhentao/baoming/pojo/Area.java
  27. 114 0
      src/main/java/com/zhentao/baoming/pojo/Evaluation.java
  28. 209 0
      src/main/java/com/zhentao/baoming/pojo/OrderActivity.java
  29. 93 0
      src/main/java/com/zhentao/baoming/pojo/OrderSignup.java
  30. 78 0
      src/main/java/com/zhentao/baoming/pojo/OrderTag.java
  31. 85 0
      src/main/java/com/zhentao/baoming/pojo/ServiceCategory.java
  32. 30 0
      src/main/java/com/zhentao/baoming/service/ActivityTagRelationService.java
  33. 26 0
      src/main/java/com/zhentao/baoming/service/AreaService.java
  34. 13 0
      src/main/java/com/zhentao/baoming/service/EvaluationService.java
  35. 58 0
      src/main/java/com/zhentao/baoming/service/OrderActivityService.java
  36. 77 0
      src/main/java/com/zhentao/baoming/service/OrderSignupService.java
  37. 20 0
      src/main/java/com/zhentao/baoming/service/OrderTagService.java
  38. 13 0
      src/main/java/com/zhentao/baoming/service/ServiceCategoryService.java
  39. 68 0
      src/main/java/com/zhentao/baoming/service/impl/ActivityTagRelationServiceImpl.java
  40. 92 0
      src/main/java/com/zhentao/baoming/service/impl/AreaServiceImpl.java
  41. 22 0
      src/main/java/com/zhentao/baoming/service/impl/EvaluationServiceImpl.java
  42. 617 0
      src/main/java/com/zhentao/baoming/service/impl/OrderActivityServiceImpl.java
  43. 272 0
      src/main/java/com/zhentao/baoming/service/impl/OrderSignupServiceImpl.java
  44. 42 0
      src/main/java/com/zhentao/baoming/service/impl/OrderTagServiceImpl.java
  45. 22 0
      src/main/java/com/zhentao/baoming/service/impl/ServiceCategoryServiceImpl.java
  46. 51 0
      src/main/java/com/zhentao/common/oss/OSSService.java
  47. 52 0
      src/main/java/com/zhentao/common/utils/Results.java
  48. 3 1
      src/main/java/com/zhentao/common/utils/TokenUtils.java
  49. 49 0
      src/main/java/com/zhentao/common/vo/ResultVo.java
  50. 1 0
      src/main/java/com/zhentao/community/dto/PostVO.java
  51. 1 0
      src/main/java/com/zhentao/community/service/impl/PostsServiceImpl.java
  52. 33 0
      src/main/java/com/zhentao/user/controller/UserController.java
  53. 4 1
      src/main/java/com/zhentao/user/domain/User.java
  54. 14 0
      src/main/java/com/zhentao/user/dto/UserStatusDto.java
  55. 297 0
      src/main/java/com/zhentao/user/face_recognition/FaceEngineUtil.java
  56. 11 0
      src/main/java/com/zhentao/user/service/UserService.java
  57. 92 0
      src/main/java/com/zhentao/user/service/impl/UserServiceImpl.java
  58. 16 0
      src/main/resources/com/zhentao/mapper/ActivityTagRelationMapper.xml
  59. 17 0
      src/main/resources/com/zhentao/mapper/AreaMapper.xml
  60. 21 0
      src/main/resources/com/zhentao/mapper/EvaluationMapper.xml
  61. 33 0
      src/main/resources/com/zhentao/mapper/OrderActivityMapper.xml
  62. 18 0
      src/main/resources/com/zhentao/mapper/OrderSignupMapper.xml
  63. 16 0
      src/main/resources/com/zhentao/mapper/OrderTagMapper.xml
  64. 17 0
      src/main/resources/com/zhentao/mapper/ServiceCategoryMapper.xml

+ 1 - 0
ArcFace64.dat

@@ -0,0 +1 @@
+EWEPEPEOGMGTELIZJUGECKIUJDBCJTCNISGPBNHLJTJUBHEWGNAKGEGAIOHJDQAJGNCFDRFZJEDMJTIAFZGGFTBUGCBCJGFTFZEEIXFKANBSISFEISBUIIIWCDIHHBHEDWILDTEFCYGCIZDFHPCNBRAZCFANACHFHNHVFBFNFEJLHTHHIFCUIBFAISFSIEADJKECHMCAEVAGBGCHIOBCILIUHXAKICEHBPIBGVBEHWBHEAJVCKCUIRCJETAIJJCFENEJFRBJDQJSDFACHBIHBLDEHNGNBSGFCRFWGYEMCGHSBCHAEXAMBSIBDKAPDRJNDEECBOHZBSAGEOAMEEBWJUBBFPDZHEJIJCBGHNGOIFBGCEJIAYHIDVJGFJGAEMEMDHFHHMDTEVAXAHCNEFDHCIFTCUGIAJEGDWETDNHLEXCMCGFCIRGFATEFGOGFHWHOBOHMIDBSAYBECWGTDVADBGCTGGIDEQGOEPCTENHMEAAXDOETBRCVBXJLJOHUIYEDBKBGHABFDAJQEKAIDCFPJEJNBAJTDODCCRHXDKEAASHXAZDVBHJDCGDWAYILCPGFDGGHFFGYFGGZHUGWEXDSBTFNADCOHSAVBKDHAODOCZCJGJJVECBQFKEYCOHWIUEMBACZCAFMHJCEGRBZBVBHBGJSAZDIBTDNAHFGAHDXEGDHDCEUFTFEDOECBDAPBYGEAEDZGNHYGWAZCEBRDCAJIEIIFYFLGHGTGWAGCJAPACFTJSFBFLFWJJCOBEATIDDAIHJHFIAYAUCJHBAGCECQJLEMHGFWHZFIHSDEANBMHGBNFWFTGTDRFDCBGRCBCQASCTDMJRBLDKDBGFDMBJEXBYCDBRFSIREPIOBJJRCIEFAPHLEJCCGJCNGCAVGLETEUJMFIANEFHVEVINANGXDHDAGHAOCSGTHXEJFKCFECFNFMDTCQGYDPGRACICCUCFGADGGWERJRCAEDGNDJDICEATHMAAFFCSIIHJFSBCCTCZGEADGZDWJRIXAVDJDFAHJHARDTAUDVGPCCCZEEJOHIEGBBFSDIJAFPCKFKFBFPDMGRIKFMFRHKICHFJJJDDFFJGQHCEYEWBXEIJDFNHGJCACCFIBDHBXGMGOFOEJAWAFIKCGAIBIFXHDEFGNFVAECVIQGKBUBZDGEWFFIODGJLCIBJFPIMJAJGDZBHITEQEJBGFO

BIN
libs/WIN64/libarcsoft_face.dll


BIN
libs/WIN64/libarcsoft_face_engine.dll


BIN
libs/WIN64/libarcsoft_face_engine_jni.dll


BIN
libs/arcsoft-sdk-face-3.0.0.0.jar


+ 9 - 1
pom.xml

@@ -16,6 +16,13 @@
     </properties>
     <dependencies>
         <dependency>
+            <groupId>face</groupId>
+            <artifactId>face</artifactId>
+            <version>3.0.0</version>
+            <scope>system</scope>
+            <systemPath>${arcsoft.lib.path}/arcsoft-sdk-face-3.0.0.0.jar</systemPath>
+        </dependency>
+        <dependency>
             <groupId>com.google.zxing</groupId>
             <artifactId>core</artifactId>
             <version>3.5.1</version>
@@ -202,7 +209,8 @@
                 <version>${spring-boot.version}</version>
                 <configuration>
                     <mainClass>com.zhentao.ZyzApplication</mainClass>
-                    <skip>true</skip>
+                    <!-- 移除skip=true配置 -->
+                    <executable>true</executable>
                 </configuration>
                 <executions>
                     <execution>

+ 1 - 0
src/main/java/com/zhentao/ZyzApplication.java

@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerIntercept
 import org.mybatis.spring.annotation.MapperScan;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
 
 import javax.annotation.ManagedBean;
 import javax.annotation.PostConstruct;

+ 104 - 0
src/main/java/com/zhentao/baoming/controller/AreaController.java

@@ -0,0 +1,104 @@
+package com.zhentao.baoming.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zhentao.baoming.pojo.Area;
+import com.zhentao.baoming.service.AreaService;
+import com.zhentao.common.config.NullLogin;
+import com.zhentao.common.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * 区域控制器
+ */
+@RestController
+@RequestMapping("/order/area")
+@CrossOrigin
+public class AreaController {
+
+    @Autowired
+    private AreaService areaService;
+
+    /**
+     * 获取所有省级区域
+     */
+    @NullLogin
+    @GetMapping("/provinces")
+    public ResultVo<List<Area>> getProvinces() {
+        try {
+            LambdaQueryWrapper<Area> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(Area::getLevel, 0)
+                    .orderByAsc(Area::getId);
+            List<Area> provinces = areaService.list(queryWrapper);
+            return ResultVo.success(provinces);
+        } catch (Exception e) {
+            return ResultVo.error(500, "获取省份列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取指定省份的城市
+     */
+    @NullLogin
+    @GetMapping("/cities/{provinceId}")
+    public ResultVo<List<Area>> getCities(@PathVariable Integer provinceId) {
+        try {
+            LambdaQueryWrapper<Area> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(Area::getParentId, provinceId)
+                    .eq(Area::getLevel, 1)
+                    .orderByAsc(Area::getId);
+            List<Area> cities = areaService.list(queryWrapper);
+            return ResultVo.success(cities);
+        } catch (Exception e) {
+            return ResultVo.error(500, "获取城市列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取指定城市的区县
+     */
+    @NullLogin
+    @GetMapping("/districts/{cityId}")
+    public ResultVo<List<Area>> getDistricts(@PathVariable Integer cityId) {
+        try {
+            LambdaQueryWrapper<Area> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(Area::getParentId, cityId)
+                    .eq(Area::getLevel, 2)
+                    .orderByAsc(Area::getId);
+            List<Area> districts = areaService.list(queryWrapper);
+            return ResultVo.success(districts);
+        } catch (Exception e) {
+            return ResultVo.error(500, "获取区县列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 根据区域ID获取完整的省市区路径
+     */
+    @NullLogin
+    @GetMapping("/path/{areaId}")
+    public ResultVo<List<Area>> getAreaPath(@PathVariable Integer areaId) {
+        try {
+            List<Area> path = areaService.getAreaPath(areaId);
+            return ResultVo.success(path);
+        } catch (Exception e) {
+            return ResultVo.error(500, "获取区域路径失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取所有区域列表(树形结构)
+     */
+    @NullLogin
+    @GetMapping("/tree")
+    public ResultVo<List<Area>> getAreaTree() {
+        try {
+            List<Area> tree = areaService.getAreaTree();
+            return ResultVo.success(tree);
+        } catch (Exception e) {
+            return ResultVo.error(500, "获取区域树失败:" + e.getMessage());
+        }
+    }
+}

+ 453 - 0
src/main/java/com/zhentao/baoming/controller/OrderActivityController.java

@@ -0,0 +1,453 @@
+package com.zhentao.baoming.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zhentao.baoming.dto.common.PageResult;
+import com.zhentao.baoming.dto.order.OrderActivityDTO;
+import com.zhentao.baoming.dto.order.OrderActivityDetailDTO;
+import com.zhentao.baoming.dto.order.OrderActivityQueryDTO;
+import com.zhentao.baoming.mapper.AreaMapper;
+import com.zhentao.baoming.mapper.OrderActivityMapper;
+import com.zhentao.baoming.pojo.Area;
+import com.zhentao.baoming.pojo.OrderActivity;
+import com.zhentao.baoming.pojo.OrderSignup;
+import com.zhentao.baoming.pojo.OrderTag;
+import com.zhentao.baoming.service.ActivityTagRelationService;
+import com.zhentao.baoming.service.OrderActivityService;
+import com.zhentao.baoming.service.OrderSignupService;
+import com.zhentao.baoming.service.OrderTagService;
+import com.zhentao.common.config.NullLogin;
+import com.zhentao.common.utils.Results;
+import com.zhentao.common.vo.ResultVo;
+import com.zhentao.user.domain.User;
+import com.zhentao.user.service.UserService;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 点单活动控制器
+ */
+@RestController
+@RequestMapping("/order/activity")
+public class OrderActivityController {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderActivityController.class);
+
+    private static final String ACTIVITY_LIST_CACHE_PREFIX = "activity:list:";
+    private static final String ACTIVITY_LIST_LOCK_PREFIX = "activity:list:lock:";
+    private static final long LIST_CACHE_EXPIRE_TIME = 5; // 缓存5分钟
+    private static final long LIST_LOCK_WAIT_TIME = 2; // 锁等待2秒
+    private static final long LIST_LOCK_LEASE_TIME = 5; // 锁租约5秒
+
+    @Autowired
+    private OrderActivityService orderActivityService;
+
+    @Autowired
+    private UserService userService;
+
+    @Autowired
+    private OrderTagService orderTagService;
+
+    @Autowired
+    private ActivityTagRelationService activityTagRelationService;
+
+    @Autowired
+    private OrderSignupService orderSignupService;
+
+    @Autowired
+    private AreaMapper areaMapper;
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Autowired
+    private RedissonClient redissonClient;
+
+    @Autowired
+    private OrderActivityMapper orderActivityMapper;
+
+    /**
+     * 获取活动列表
+     */
+    @NullLogin
+    @GetMapping("/list")
+    public ResultVo<PageResult<OrderActivityDTO>> list(
+            @RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
+            @RequestParam(value = "pageSize", defaultValue = "10") Integer pageSize,
+            @RequestParam(value = "areaId", required = false, defaultValue = "0") Integer areaId,
+            @RequestParam(value = "categoryId", required = false, defaultValue = "0") Integer categoryId) {
+
+        log.info("查询活动列表:pageNum={}, pageSize={}, areaId={}, categoryId={}", pageNum, pageSize, areaId, categoryId);
+
+        try {
+            // 构建查询条件
+            OrderActivityQueryDTO queryDTO = new OrderActivityQueryDTO();
+            queryDTO.setPageNum(pageNum);
+            queryDTO.setPageSize(pageSize);
+            queryDTO.setAreaId(areaId > 0 ? areaId : null);
+            queryDTO.setCategoryId(categoryId > 0 ? categoryId : null);
+
+            // 直接调用服务查询活动列表
+            PageResult<OrderActivityDTO> result = orderActivityService.queryActivityList(queryDTO);
+
+            // 记录查询结果
+            log.info("查询活动列表成功,总数:{}", result.getTotal());
+
+            // 检查返回的活动数据中是否包含类别信息
+            if (result != null && result.getList() != null && !result.getList().isEmpty()) {
+                for (OrderActivityDTO activity : result.getList()) {
+                    // 添加更详细的日志
+                    log.info("活动详情 - ID: {}, 标题: {}, 报名人数: {}/{}, 状态: {}, 类别: {}",
+                        activity.getId(),
+                        activity.getTitle(),
+                        activity.getCurrentNumber(),
+                        activity.getRecruitNumber(),
+                        activity.getStatus(),
+                        activity.getCategoryName());
+
+                    // 查询实际报名人数进行对比
+                    LambdaQueryWrapper<OrderSignup> signupQuery = new LambdaQueryWrapper<>();
+                    signupQuery.eq(OrderSignup::getOrderId, activity.getId())
+                            .in(OrderSignup::getStatus, 0, 1); // 已报名或已通过状态
+                    long actualSignups = orderSignupService.count(signupQuery);
+
+                    // 如果数据不一致,记录警告日志
+                    if (activity.getCurrentNumber() == null || activity.getCurrentNumber() != actualSignups) {
+                        log.warn("活动 [{}] 的报名人数不一致!数据库值: {}, 实际报名人数: {}",
+                            activity.getTitle(),
+                            activity.getCurrentNumber(),
+                            actualSignups);
+                    }
+                }
+            } else {
+                log.warn("活动列表为空或未包含数据");
+            }
+
+            // 返回成功响应
+            return ResultVo.success(result);
+        } catch (Exception e) {
+            log.error("查询活动列表异常", e);
+            return ResultVo.error(500, "查询活动列表失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 获取活动详情
+     */
+    @NullLogin// 允许用户未登录访问
+    @GetMapping("/{id}")
+    public ResultVo<OrderActivityDetailDTO> getActivityDetail(@PathVariable("id") Integer id) {
+        log.debug("获取活动详情,ID: {}", id);
+        OrderActivityDetailDTO detail = orderActivityService.getActivityDetail(id);
+        System.out.println(detail);
+        if (detail == null) {
+            return ResultVo.error(404, "活动不存在");
+        }
+
+        // 确保招募人数字段不为null
+        if (detail.getRecruitNumber() == null) {
+            detail.setRecruitNumber(20); // 默认招募20人
+        }
+
+        // 查询实际报名人数
+        long signupCount = countSignupsByActivityId(id);
+        detail.setCurrentNumber((int) signupCount);
+
+        // 检查是否报名已满,如果已满且状态为待接单,则自动更新为已接单
+        if (signupCount >= detail.getRecruitNumber() && detail.getConnect() == 0) {
+            // 更新活动状态
+            OrderActivity activity = orderActivityService.getById(id);
+            if (activity != null && activity.getConnect() == 0) {
+                activity.setConnect(1); // 设置为已接单
+                orderActivityService.updateById(activity);
+                detail.setConnect(1); // 更新返回结果中的状态
+                log.info("活动 [{}] 报名已满,自动更新状态为已接单", activity.getTitle());
+            }
+        }
+
+        log.debug("活动详情数据: recruitNumber={}, currentNumber={}",
+                 detail.getRecruitNumber(), detail.getCurrentNumber());
+
+        return ResultVo.success(detail);
+    }
+
+    /**
+     * 根据用户ID查询发布的活动
+     */
+    @NullLogin// 允许用户未登录访问
+    @GetMapping("/user/{userId}")
+    public ResultVo<List<OrderActivityDTO>> getActivitiesByUser(@PathVariable("userId") Long userId) {
+        // 构建查询条件
+        LambdaQueryWrapper<OrderActivity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(OrderActivity::getPublisherId, userId);
+        wrapper.orderByDesc(OrderActivity::getCreateTime);
+
+        // 执行查询
+        List<OrderActivity> activities = orderActivityService.list(wrapper);
+
+        // 转换为DTO
+        List<OrderActivityDTO> activityDTOs = activities.stream().map(activity -> {
+            OrderActivityDTO dto = new OrderActivityDTO();
+            BeanUtils.copyProperties(activity, dto);
+
+            // 获取发布者信息
+            User publisher = userService.getById(activity.getPublisherId());
+            if (publisher != null) {
+                dto.setPublisherName(publisher.getRealName());
+                dto.setPublisherAvatar(publisher.getAvatar());
+                dto.setPublisherRating(publisher.getRating() != null ? publisher.getRating().intValue() : 0);
+            }
+
+            // 获取活动标签
+            List<Integer> tagIds = activityTagRelationService.getTagIdsByActivityId(activity.getId());
+            if (tagIds != null && !tagIds.isEmpty()) {
+                List<OrderTag> tags = orderTagService.listByIds(tagIds);
+                String tagNames = tags.stream().map(OrderTag::getName).collect(Collectors.joining(","));
+                dto.setTags(tagNames);
+                dto.setTagIds(tagIds);
+            }
+
+            // 设置地址信息
+            if (activity.getAreaId() != null) {
+               Area area = areaMapper.selectById(activity.getAreaId());
+                if (area != null) {
+                    dto.setAreaName(area.getName());
+                }
+            }
+            if (activity.getCurrentNumber()!=null){
+                dto.setCurrentNumber(activity.getCurrentNumber());
+            }
+
+            // 设置报名状态
+            if (activity.getEnrollmentStatus() != null) {
+                dto.setEnrollmentStatus(activity.getEnrollmentStatus());
+            } else {
+                dto.setEnrollmentStatus(0); // 默认为未报名状态
+            }
+
+            // 设置详细地址
+            dto.setLocation(activity.getAddress());
+
+            // 计算已报名人数
+            long signupCount = countSignupsByActivityId(activity.getId());
+            dto.setSignupNumber((int) signupCount);
+
+            return dto;
+        }).collect(Collectors.toList());
+
+        return ResultVo.success(activityDTOs);
+    }
+
+    /**
+     * 获取热门发布者
+     */
+    @NullLogin
+    @GetMapping("/popular-publishers")
+    public ResultVo<List<Map<String, Object>>> getPopularPublishers() {
+        // 获取热门发布者列表(前10名)
+        List<Map<String, Object>> publishers = new ArrayList<>();
+
+        // 查询所有发布者,按活动数量排序
+        Map<Long, Integer> publisherCounts = new HashMap<>();
+        List<OrderActivity> allActivities = orderActivityService.list();
+        for (OrderActivity activity : allActivities) {
+            Long publisherId = activity.getPublisherId();
+            publisherCounts.put(publisherId, publisherCounts.getOrDefault(publisherId, 0) + 1);
+        }
+
+        // 排序取前10
+        List<Map.Entry<Long, Integer>> sortedPublishers = new ArrayList<>(publisherCounts.entrySet());
+        sortedPublishers.sort((e1, e2) -> e2.getValue() - e1.getValue());
+
+        // 获取前10个发布者的详细信息
+        int count = 0;
+        for (Map.Entry<Long, Integer> entry : sortedPublishers) {
+            if (count >= 10) break;
+
+            Long publisherId = entry.getKey();
+            Integer activityCount = entry.getValue();
+
+            User user = userService.getById(publisherId);
+            if (user != null) {
+                Map<String, Object> publisherInfo = new HashMap<>();
+                publisherInfo.put("id", user.getId());
+                publisherInfo.put("realName", user.getRealName());
+                publisherInfo.put("avatar", user.getAvatar());
+                publisherInfo.put("rating", user.getRating());
+                publisherInfo.put("activityCount", activityCount);
+                publishers.add(publisherInfo);
+                count++;
+            }
+        }
+
+        return ResultVo.success(publishers);
+    }
+
+    /**
+     * 查询用户发布的活动统计信息
+     */
+    @NullLogin
+    @GetMapping("/user/{userId}/stats")
+    public ResultVo<Map<String, Object>> getUserActivityStats(@PathVariable("userId") Long userId) {
+        Map<String, Object> stats = new HashMap<>();
+
+        // 查询用户发布的活动总数
+        LambdaQueryWrapper<OrderActivity> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(OrderActivity::getPublisherId, userId);
+        long totalActivities = orderActivityService.count(wrapper);
+        stats.put("totalActivities", totalActivities);
+
+        // 查询用户发布的待接单活动数
+        LambdaQueryWrapper<OrderActivity> waitingWrapper = new LambdaQueryWrapper<>();
+        waitingWrapper.eq(OrderActivity::getPublisherId, userId);
+        waitingWrapper.eq(OrderActivity::getStatus, 0); // 假设0表示待接单
+        long waitingActivities = orderActivityService.count(waitingWrapper);
+        stats.put("waitingActivities", waitingActivities);
+
+        // 查询用户发布的已接单活动数
+        LambdaQueryWrapper<OrderActivity> acceptedWrapper = new LambdaQueryWrapper<>();
+        acceptedWrapper.eq(OrderActivity::getPublisherId, userId);
+        acceptedWrapper.eq(OrderActivity::getStatus, 1); // 假设1表示已接单
+        long acceptedActivities = orderActivityService.count(acceptedWrapper);
+        stats.put("acceptedActivities", acceptedActivities);
+
+        // 查询用户信息
+        User user = userService.getById(userId);
+        if (user != null) {
+            Map<String, Object> userInfo = new HashMap<>();
+            userInfo.put("id", user.getId());
+            userInfo.put("realName", user.getRealName());
+            userInfo.put("avatar", user.getAvatar());
+            userInfo.put("rating", user.getRating());
+            stats.put("userInfo", userInfo);
+        }
+
+        return ResultVo.success(stats);
+    }
+
+    /**
+     * 发布新活动
+     */
+    @PostMapping("/publish")
+    public ResultVo<OrderActivity> publishActivity(@RequestBody OrderActivityDTO activityDTO, HttpServletRequest request) {
+        // 获取当前登录用户ID
+        String userId = request.getHeader("userId");
+        if (userId == null) {
+            return ResultVo.error(401, "请先登录");
+        }
+
+        // 创建活动对象
+        OrderActivity activity = new OrderActivity();
+        BeanUtils.copyProperties(activityDTO, activity);
+
+        // 设置发布者ID
+        activity.setPublisherId(Long.valueOf(userId));
+
+        // 设置初始状态为待接单
+        activity.setStatus(0); // 假设0表示待接单状态
+
+        // 保存活动
+        boolean success = orderActivityService.save(activity);
+        if (!success) {
+            return ResultVo.error(500, "发布活动失败");
+        }
+
+        // 保存活动标签关联
+        if (activityDTO.getTagIds() != null && !activityDTO.getTagIds().isEmpty()) {
+            boolean tagsSaved = activityTagRelationService.saveBatchByActivityId(activity.getId(), activityDTO.getTagIds());
+            if (!tagsSaved) {
+                return ResultVo.error(500, "保存活动标签失败");
+            }
+        }
+
+        return ResultVo.success(activity);
+    }
+
+    /**
+     * 计算活动报名人数
+     */
+    private long countSignupsByActivityId(Integer activityId) {
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getOrderId, activityId);
+//                   .in(OrderSignup::getStatus, 0, 1); // 只计算状态为0(已报名)和1(已通过)的报名记录
+        long count = orderSignupService.count(queryWrapper);
+        log.info("活动ID={}的有效报名人数: {}", activityId, count);
+        return count;
+    }
+
+    /**
+     * 修复所有活动的报名人数
+     * 这个接口应该只允许管理员访问
+     */
+    @PostMapping("/fix-signup-numbers")
+    public ResultVo<String> fixSignupNumbers() {
+        try {
+            orderActivityService.fixAllActivitySignupNumbers();
+            return ResultVo.success("修复完成");
+        } catch (Exception e) {
+            log.error("修复活动报名人数失败", e);
+            return ResultVo.error(500, "修复失败:" + e.getMessage());
+        }
+    }
+
+    /**
+     * 诊断活动报名人数
+     */
+    @GetMapping("/diagnose-signup-numbers")
+    public ResultVo<List<Map<String, Object>>> diagnoseSignupNumbers() {
+        try {
+            List<Map<String, Object>> diagnosis = orderActivityMapper.checkSignupNumbers();
+
+            // 记录诊断结果
+            for (Map<String, Object> activity : diagnosis) {
+                Object currentNumber = activity.get("current_number");
+                Object actualSignups = activity.get("actual_signups");
+
+                log.info("活动诊断 - ID: {}, 标题: {}, 数据库报名人数: {}, 实际报名人数: {}",
+                    activity.get("id"),
+                    activity.get("title"),
+                    currentNumber,
+                    actualSignups);
+
+                // 检查是否有不一致
+                if (currentNumber == null || !currentNumber.equals(actualSignups)) {
+                    log.warn("发现报名人数不一致!活动ID: {}, 标题: {}, 数据库值: {}, 实际值: {}",
+                        activity.get("id"),
+                        activity.get("title"),
+                        currentNumber,
+                        actualSignups);
+                }
+            }
+
+            return ResultVo.success(diagnosis);
+        } catch (Exception e) {
+            log.error("诊断活动报名人数失败", e);
+            return ResultVo.error(500, "诊断失败:" + e.getMessage());
+        }
+    }
+
+    @GetMapping("/diagnose-signup-numbers/{activityId}")
+    @ResponseBody
+    public Results<Map<String, Object>> diagnoseSignupNumbers(@PathVariable Integer activityId) {
+        Map<String, Object> diagnosis = orderActivityService.diagnoseSignupNumbers(activityId);
+        return Results.success(diagnosis);
+    }
+
+    @PostMapping("/fix-signup-numbers/{activityId}")
+    @ResponseBody
+    public Results<Boolean> fixSignupNumbers(@PathVariable Integer activityId) {
+        boolean fixed = orderActivityService.fixSignupNumbers(activityId);
+        return Results.success(fixed);
+    }
+}

+ 135 - 0
src/main/java/com/zhentao/baoming/controller/OrderSignupController.java

@@ -0,0 +1,135 @@
+package com.zhentao.baoming.controller;
+
+import com.zhentao.baoming.dto.order.OrderSignupDTO;
+import com.zhentao.baoming.service.OrderActivityService;
+import com.zhentao.baoming.service.OrderSignupService;
+import com.zhentao.common.vo.ResultVo;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.web.bind.annotation.*;
+
+import javax.servlet.http.HttpServletRequest;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 点单报名控制器
+ */
+@RestController
+@RequestMapping("/order/signup")
+@Slf4j
+public class OrderSignupController {
+
+    @Autowired
+    private OrderSignupService orderSignupService;
+
+    @Autowired
+    private OrderActivityService orderActivityService;
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 获取用户的报名列表
+     */
+    @GetMapping("/user")
+    public ResultVo<List<OrderSignupDTO>> getUserSignupList(HttpServletRequest request) {
+        // 获取当前登录用户ID
+        String userIdStr = request.getHeader("userId");
+        if (userIdStr == null) {
+            // 尝试从请求属性中获取
+            Object userId = request.getAttribute("userId");
+            if (userId != null) {
+                userIdStr = userId.toString();
+            }
+        }
+        
+        if (userIdStr == null) {
+            return ResultVo.error(401, "请先登录");
+        }
+
+        Long userId = Long.valueOf(userIdStr);
+        List<OrderSignupDTO> signupList = orderSignupService.getUserSignupList(userId);
+
+        return ResultVo.success(signupList);
+    }
+
+    /**
+     * 获取活动的报名列表
+     */
+    @GetMapping("/activity/{activityId}")
+    public ResultVo<List<OrderSignupDTO>> getActivitySignupList(@PathVariable("activityId") Integer activityId) {
+        List<OrderSignupDTO> signupList = orderSignupService.getActivitySignupList(activityId);
+        return ResultVo.success(signupList);
+    }
+
+    /**
+     * 报名活动
+     */
+    @PostMapping("/apply")
+    public ResultVo<Boolean> applyActivity(@RequestBody Map<String, Object> params, HttpServletRequest request) {
+        // 获取当前登录用户ID
+        String userIdStr = request.getHeader("userId");
+        if (userIdStr == null) {
+            // 尝试从请求属性中获取
+            Object userId = request.getAttribute("userId");
+            if (userId != null) {
+                userIdStr = userId.toString();
+            }
+        }
+        
+        System.out.println("报名接口获取到的userId: " + userIdStr);
+        
+        if (userIdStr == null) {
+            return ResultVo.error(401, "请先登录");
+        }
+
+        // 获取活动ID和备注
+        Integer activityId = Integer.valueOf(params.get("orderId").toString());
+        String comment = params.get("comment") != null ? params.get("comment").toString() : "";
+
+        // 报名活动
+        Long userId = Long.valueOf(userIdStr);
+        boolean success = orderSignupService.signupActivity(userId, activityId, comment);
+
+        if (success) {
+            return ResultVo.success(true);
+        } else {
+            return ResultVo.error(500, "报名失败");
+        }
+    }
+
+    /**
+     * 取消报名
+     */
+    @PostMapping("/cancel")
+    public ResultVo<Boolean> cancelSignup(@RequestBody Map<String, Object> params, HttpServletRequest request) {
+        // 获取当前登录用户ID
+        String userIdStr = request.getHeader("userId");
+        if (userIdStr == null) {
+            // 尝试从请求属性中获取
+            Object userId = request.getAttribute("userId");
+            if (userId != null) {
+                userIdStr = userId.toString();
+            }
+        }
+        
+        if (userIdStr == null) {
+            return ResultVo.error(401, "请先登录");
+        }
+
+        // 获取活动ID
+        Integer activityId = Integer.valueOf(params.get("orderId").toString());
+
+        // 取消报名
+        Long userId = Long.valueOf(userIdStr);
+        boolean success = orderSignupService.cancelSignup(userId, activityId);
+
+        if (success) {
+            return ResultVo.success(true);
+        } else {
+            return ResultVo.error(500, "取消报名失败");
+        }
+    }
+}

+ 60 - 0
src/main/java/com/zhentao/baoming/controller/OrderTagController.java

@@ -0,0 +1,60 @@
+package com.zhentao.baoming.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zhentao.baoming.pojo.OrderTag;
+import com.zhentao.baoming.service.OrderTagService;
+import com.zhentao.common.config.NullLogin;
+import com.zhentao.common.vo.ResultVo;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 点单标签控制器
+ */
+@RestController
+@RequestMapping("/order/tag")
+public class OrderTagController {
+
+    @Autowired
+    private OrderTagService orderTagService;
+
+    /**
+     * 获取所有标签
+     */
+    @NullLogin
+    @GetMapping("/list")
+    public ResultVo<List<OrderTag>> getAllTags() {
+        LambdaQueryWrapper<OrderTag> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByDesc(OrderTag::getCreateTime);  // 按创建时间降序排列
+        List<OrderTag> tags = orderTagService.list(queryWrapper);
+        return ResultVo.success(tags);
+    }
+
+    /**
+     * 根据ID获取标签
+     */
+    @NullLogin
+    @GetMapping("/{id}")
+    public ResultVo<OrderTag> getTagById(@PathVariable Integer id) {
+        OrderTag tag = orderTagService.getById(id);
+        if (tag == null) {
+            return ResultVo.error(404, "标签不存在");
+        }
+        return ResultVo.success(tag);
+    }
+
+    /**
+     * 根据活动ID获取相关标签
+     */
+    @NullLogin
+    @GetMapping("/activity/{activityId}")
+    public ResultVo<List<OrderTag>> getTagsByActivityId(@PathVariable Integer activityId) {
+        List<OrderTag> tags = orderTagService.getTagsByActivityId(activityId);
+        return ResultVo.success(tags);
+    }
+}

+ 71 - 0
src/main/java/com/zhentao/baoming/controller/ServiceCategoryController.java

@@ -0,0 +1,71 @@
+package com.zhentao.baoming.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.zhentao.baoming.pojo.ServiceCategory;
+import com.zhentao.baoming.service.ServiceCategoryService;
+import com.zhentao.common.config.NullLogin;
+import com.zhentao.common.vo.ResultVo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+/**
+ * 服务类别控制器
+ */
+@RestController
+@RequestMapping("/service/category")
+public class ServiceCategoryController {
+
+    private static final Logger log = LoggerFactory.getLogger(ServiceCategoryController.class);
+
+    @Autowired
+    private ServiceCategoryService serviceCategoryService;
+
+    /**
+     * 获取所有服务类别
+     */
+    @NullLogin
+    @GetMapping("/list")
+    public ResultVo<List<ServiceCategory>> getAllCategories() {
+        log.info("开始获取所有服务类别");
+        
+        LambdaQueryWrapper<ServiceCategory> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.orderByAsc(ServiceCategory::getSortOrder);  // 按排序字段升序排列
+        List<ServiceCategory> categories = serviceCategoryService.list(queryWrapper);
+        
+        if (categories != null && !categories.isEmpty()) {
+            log.info("成功获取 {} 个服务类别", categories.size());
+            for (ServiceCategory category : categories) {
+                log.info("服务类别: id={}, name={}", category.getId(), category.getName());
+            }
+        } else {
+            log.warn("未找到任何服务类别");
+        }
+        
+        return ResultVo.success(categories);
+    }
+
+    /**
+     * 根据ID获取服务类别
+     */
+    @NullLogin
+    @GetMapping("/{id}")
+    public ResultVo<ServiceCategory> getCategoryById(@PathVariable Integer id) {
+        log.info("根据ID获取服务类别: id={}", id);
+        
+        ServiceCategory category = serviceCategoryService.getById(id);
+        if (category == null) {
+            log.warn("服务类别不存在: id={}", id);
+            return ResultVo.error(404, "服务类别不存在");
+        }
+        
+        log.info("成功获取服务类别: id={}, name={}", category.getId(), category.getName());
+        return ResultVo.success(category);
+    }
+}

+ 103 - 0
src/main/java/com/zhentao/baoming/dto/common/PageResult.java

@@ -0,0 +1,103 @@
+package com.zhentao.baoming.dto.common;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 分页结果类
+ * @param <T> 数据类型
+ */
+public class PageResult<T> implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 总记录数
+     */
+    private long total;
+
+    /**
+     * 每页记录数
+     */
+    private int pageSize;
+
+    /**
+     * 当前页码
+     */
+    private int pageNum;
+
+    /**
+     * 总页数
+     */
+    private int pages;
+
+    /**
+     * 数据列表
+     */
+    private List<T> list;
+
+    public PageResult() {
+    }
+
+    public PageResult(long total, int pageSize, int pageNum, int pages, List<T> list) {
+        this.total = total;
+        this.pageSize = pageSize;
+        this.pageNum = pageNum;
+        this.pages = pages;
+        this.list = list;
+    }
+
+    /**
+     * 创建分页结果
+     * @param total 总记录数
+     * @param pageSize 每页记录数
+     * @param pageNum 当前页码
+     * @param list 数据列表
+     * @param <T> 数据类型
+     * @return 分页结果
+     */
+    public static <T> PageResult<T> of(long total, int pageSize, int pageNum, List<T> list) {
+        int pages = (int) Math.ceil((double) total / pageSize);
+        return new PageResult<>(total, pageSize, pageNum, pages, list);
+    }
+
+    public long getTotal() {
+        return total;
+    }
+
+    public void setTotal(long total) {
+        this.total = total;
+    }
+
+    public int getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(int pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public int getPageNum() {
+        return pageNum;
+    }
+
+    public void setPageNum(int pageNum) {
+        this.pageNum = pageNum;
+    }
+
+    public int getPages() {
+        return pages;
+    }
+
+    public void setPages(int pages) {
+        this.pages = pages;
+    }
+
+    public List<T> getList() {
+        return list;
+    }
+
+    public void setList(List<T> list) {
+        this.list = list;
+    }
+}

+ 286 - 0
src/main/java/com/zhentao/baoming/dto/order/OrderActivityDTO.java

@@ -0,0 +1,286 @@
+package com.zhentao.baoming.dto.order;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 订单活动DTO,用于订单列表展示
+ */
+public class OrderActivityDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 订单活动ID
+     */
+    private Integer id;
+
+    /**
+     * 活动标题
+     */
+    private String title;
+
+    /**
+     * 活动图片
+     */
+    private String coverImage;
+
+    /**
+     * 活动地点
+     */
+    private String location;
+
+    /**
+     * 区域ID
+     */
+    private Integer areaId;
+
+    /**
+     * 区域名称
+     */
+    private String areaName;
+
+    /**
+     * 服务类别ID
+     */
+    private Integer categoryId;
+
+    /**
+     * 服务类别名称
+     */
+    private String categoryName;
+
+    /**
+     * 活动时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date activityTime;
+
+    /**
+     * 发布者ID
+     */
+    private Long publisherId;
+
+    /**
+     * 发布者姓名
+     */
+    private String publisherName;
+
+    /**
+     * 发布者头像
+     */
+    private String publisherAvatar;
+
+    /**
+     * 发布者星级评分
+     */
+    private Integer publisherRating;
+
+    /**
+     * 订单状态(0-待接单,1-招募中,2-已接单,3-已完成)
+     */
+    private Integer status;
+
+    /**
+     * 接单状态(0-待接单,1-已接单,2-已完成,3-已取消)
+     */
+    private Integer connect;
+
+    /**
+     * 报名状态:0-待报名,1-已报名,2-已取消
+     */
+    private Integer enrollmentStatus;
+    /**
+     * 招募人数
+     */
+    private Integer recruitNumber;
+    /**
+     * 当前报名人数
+     */
+    private Integer currentNumber;
+
+    /**
+     * 当前报名人数
+     */
+    private Integer signupNumber;
+
+    /**
+     * 标签列表,逗号分隔
+     */
+    private String tags;
+
+    /**
+     * 标签ID列表
+     */
+    private List<Integer> tagIds;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getCoverImage() {
+        return coverImage;
+    }
+
+    public void setCoverImage(String coverImage) {
+        this.coverImage = coverImage;
+    }
+
+    public String getLocation() {
+        return location;
+    }
+
+    public void setLocation(String location) {
+        this.location = location;
+    }
+
+    public Integer getCurrentNumber() {
+        return currentNumber;
+    }
+
+    public void setCurrentNumber(Integer currentNumber) {
+        this.currentNumber = currentNumber;
+    }
+
+    public Integer getAreaId() {
+        return areaId;
+    }
+
+    public void setAreaId(Integer areaId) {
+        this.areaId = areaId;
+    }
+
+    public String getAreaName() {
+        return areaName;
+    }
+
+    public void setAreaName(String areaName) {
+        this.areaName = areaName;
+    }
+
+    public Integer getCategoryId() {
+        return categoryId;
+    }
+
+    public void setCategoryId(Integer categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public void setCategoryName(String categoryName) {
+        this.categoryName = categoryName;
+    }
+
+    public Date getActivityTime() {
+        return activityTime;
+    }
+
+    public void setActivityTime(Date activityTime) {
+        this.activityTime = activityTime;
+    }
+
+    public Long getPublisherId() {
+        return publisherId;
+    }
+
+    public void setPublisherId(Long publisherId) {
+        this.publisherId = publisherId;
+    }
+
+    public String getPublisherName() {
+        return publisherName;
+    }
+
+    public void setPublisherName(String publisherName) {
+        this.publisherName = publisherName;
+    }
+
+    public String getPublisherAvatar() {
+        return publisherAvatar;
+    }
+
+    public void setPublisherAvatar(String publisherAvatar) {
+        this.publisherAvatar = publisherAvatar;
+    }
+
+    public Integer getPublisherRating() {
+        return publisherRating;
+    }
+
+    public void setPublisherRating(Integer publisherRating) {
+        this.publisherRating = publisherRating;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getConnect() {
+        return connect;
+    }
+
+    public void setConnect(Integer connect) {
+        this.connect = connect;
+    }
+    public Integer getEnrollmentStatus() {
+        return enrollmentStatus;
+    }
+    public void setEnrollmentStatus(Integer enrollmentStatus) {
+        this.enrollmentStatus = enrollmentStatus;
+    }
+    public Integer getRecruitNumber() {
+        return recruitNumber;
+    }
+
+    public void setRecruitNumber(Integer recruitNumber) {
+        this.recruitNumber = recruitNumber;
+    }
+
+    public Integer getSignupNumber() {
+        return signupNumber;
+    }
+
+    public void setSignupNumber(Integer signupNumber) {
+        this.signupNumber = signupNumber;
+    }
+
+    public String getTags() {
+        return tags;
+    }
+
+    public void setTags(String tags) {
+        this.tags = tags;
+    }
+
+    public List<Integer> getTagIds() {
+        return tagIds;
+    }
+
+    public void setTagIds(List<Integer> tagIds) {
+        this.tagIds = tagIds;
+    }
+}

+ 423 - 0
src/main/java/com/zhentao/baoming/dto/order/OrderActivityDetailDTO.java

@@ -0,0 +1,423 @@
+package com.zhentao.baoming.dto.order;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * 订单活动详情DTO
+ */
+public class OrderActivityDetailDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 活动ID
+     */
+    private Integer id;
+
+    /**
+     * 活动标题
+     */
+    private String title;
+
+    /**
+     * 活动封面图片
+     */
+    private String coverImage;
+
+    /**
+     * 活动详情内容
+     */
+    private String content;
+
+    /**
+     * 发布者ID
+     */
+    private Long publisherId;
+
+    /**
+     * 发布者姓名
+     */
+    private String publisherName;
+
+    /**
+     * 发布者头像
+     */
+    private String publisherAvatar;
+
+    /**
+     * 发布者评分
+     */
+    private Integer publisherRating;
+
+    /**
+     * 服务类别ID
+     */
+    private Integer categoryId;
+
+    /**
+     * 服务类别名称
+     */
+    private String categoryName;
+
+    /**
+     * 区域ID
+     */
+    private Integer areaId;
+
+    /**
+     * 区域名称
+     */
+    private String areaName;
+
+    /**
+     * 详细地址
+     */
+    private String address;
+
+    /**
+     * 活动时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date activityTime;
+
+    /**
+     * 招募开始时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date recruitStart;
+
+    /**
+     * 招募结束时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date recruitEnd;
+
+    /**
+     * 招募人数
+     */
+    private Integer recruitNumber;
+
+    /**
+     * 当前报名人数
+     */
+    private Integer currentNumber;
+
+    /**
+     * 活动状态(0-待接单,1-招募中,2-已接单,3-已完成,4-已取消)
+     */
+    private Integer status;
+
+    /**
+     * 接单状态(0-待接单,1-已接单,2-已完成,3-已取消)
+     */
+    private Integer connect;
+    /**
+     * 报名状态:0-待报名,1-已报名,2-已取消
+     */
+    private Integer enrollmentStatus;
+    /**
+     * 活动标签列表
+     */
+    private List<OrderTagDTO> tags;
+
+    /**
+     * 报名人员信息
+     */
+    private List<SignupUserDTO> signupUsers;
+
+    /**
+     * 创建时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     * 当前用户是否已报名
+     */
+    private Boolean isSignedUp;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public String getCoverImage() {
+        return coverImage;
+    }
+
+    public void setCoverImage(String coverImage) {
+        this.coverImage = coverImage;
+    }
+
+    public String getContent() {
+        return content;
+    }
+
+    public void setContent(String content) {
+        this.content = content;
+    }
+
+    public Long getPublisherId() {
+        return publisherId;
+    }
+
+    public void setPublisherId(Long publisherId) {
+        this.publisherId = publisherId;
+    }
+
+    public String getPublisherName() {
+        return publisherName;
+    }
+
+    public void setPublisherName(String publisherName) {
+        this.publisherName = publisherName;
+    }
+
+    public String getPublisherAvatar() {
+        return publisherAvatar;
+    }
+
+    public void setPublisherAvatar(String publisherAvatar) {
+        this.publisherAvatar = publisherAvatar;
+    }
+
+    public Integer getPublisherRating() {
+        return publisherRating;
+    }
+
+    public void setPublisherRating(Integer publisherRating) {
+        this.publisherRating = publisherRating;
+    }
+
+    public Integer getCategoryId() {
+        return categoryId;
+    }
+
+    public void setCategoryId(Integer categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public String getCategoryName() {
+        return categoryName;
+    }
+
+    public void setCategoryName(String categoryName) {
+        this.categoryName = categoryName;
+    }
+
+    public Integer getAreaId() {
+        return areaId;
+    }
+
+    public void setAreaId(Integer areaId) {
+        this.areaId = areaId;
+    }
+
+    public String getAreaName() {
+        return areaName;
+    }
+
+    public void setAreaName(String areaName) {
+        this.areaName = areaName;
+    }
+
+    public String getAddress() {
+        return address;
+    }
+
+    public void setAddress(String address) {
+        this.address = address;
+    }
+
+    public Date getActivityTime() {
+        return activityTime;
+    }
+
+    public void setActivityTime(Date activityTime) {
+        this.activityTime = activityTime;
+    }
+
+    public Date getRecruitStart() {
+        return recruitStart;
+    }
+
+    public void setRecruitStart(Date recruitStart) {
+        this.recruitStart = recruitStart;
+    }
+
+    public Date getRecruitEnd() {
+        return recruitEnd;
+    }
+
+    public void setRecruitEnd(Date recruitEnd) {
+        this.recruitEnd = recruitEnd;
+    }
+
+    public Integer getRecruitNumber() {
+        return recruitNumber;
+    }
+
+    public void setRecruitNumber(Integer recruitNumber) {
+        this.recruitNumber = recruitNumber;
+    }
+
+    public Integer getCurrentNumber() {
+        return currentNumber;
+    }
+
+    public void setCurrentNumber(Integer currentNumber) {
+        this.currentNumber = currentNumber;
+    }
+    public Integer getEnrollmentStatus() {
+        return enrollmentStatus;
+    }
+    public void setEnrollmentStatus(Integer enrollmentStatus) {
+        this.enrollmentStatus = enrollmentStatus;
+    }
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getConnect() {
+        return connect;
+    }
+
+    public void setConnect(Integer connect) {
+        this.connect = connect;
+    }
+
+    public List<OrderTagDTO> getTags() {
+        return tags;
+    }
+
+    public void setTags(List<OrderTagDTO> tags) {
+        this.tags = tags;
+    }
+
+    public List<SignupUserDTO> getSignupUsers() {
+        return signupUsers;
+    }
+
+    public void setSignupUsers(List<SignupUserDTO> signupUsers) {
+        this.signupUsers = signupUsers;
+    }
+
+    public Date getCreateTime() {
+        return createTime;
+    }
+
+    public void setCreateTime(Date createTime) {
+        this.createTime = createTime;
+    }
+
+    public Boolean getIsSignedUp() {
+        return isSignedUp;
+    }
+
+    public void setIsSignedUp(Boolean isSignedUp) {
+        this.isSignedUp = isSignedUp;
+    }
+
+    /**
+     * 标签DTO
+     */
+    public static class OrderTagDTO implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        private Integer id;
+        private String name;
+
+        public Integer getId() {
+            return id;
+        }
+
+        public void setId(Integer id) {
+            this.id = id;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+
+    /**
+     * 报名用户DTO
+     */
+    public static class SignupUserDTO implements Serializable {
+        private static final long serialVersionUID = 1L;
+
+        private Integer id;
+        private String username;
+        private String realName;
+        private String avatar;
+        private Integer status;  // 报名状态
+
+        public Integer getId() {
+            return id;
+        }
+
+        public void setId(Integer id) {
+            this.id = id;
+        }
+
+        public String getUsername() {
+            return username;
+        }
+
+        public void setUsername(String username) {
+            this.username = username;
+        }
+
+        public String getRealName() {
+            return realName;
+        }
+
+        public void setRealName(String realName) {
+            this.realName = realName;
+        }
+
+        public String getAvatar() {
+            return avatar;
+        }
+
+        public void setAvatar(String avatar) {
+            this.avatar = avatar;
+        }
+
+        public Integer getStatus() {
+            return status;
+        }
+
+        public void setStatus(Integer status) {
+            this.status = status;
+        }
+    }
+}

+ 115 - 0
src/main/java/com/zhentao/baoming/dto/order/OrderActivityQueryDTO.java

@@ -0,0 +1,115 @@
+package com.zhentao.baoming.dto.order;
+
+import java.io.Serializable;
+
+/**
+ * 活动查询条件DTO
+ */
+public class OrderActivityQueryDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 当前页码
+     */
+    private long pageNum = 1;
+
+    /**
+     * 每页大小
+     */
+    private long pageSize = 10;
+
+    /**
+     * 活动标题关键字
+     */
+    private String title;
+
+    /**
+     * 服务类别ID
+     */
+    private Integer categoryId;
+
+    /**
+     * 区域ID
+     */
+    private Integer areaId;
+
+    /**
+     * 活动状态
+     */
+    private Integer status;
+
+    /**
+     * 标签ID
+     */
+    private Integer tagId;
+
+    /**
+     * 发布者ID
+     */
+    private Long publisherId;
+
+    public long getPageNum() {
+        return pageNum;
+    }
+
+    public void setPageNum(long pageNum) {
+        this.pageNum = pageNum;
+    }
+
+    public long getPageSize() {
+        return pageSize;
+    }
+
+    public void setPageSize(long pageSize) {
+        this.pageSize = pageSize;
+    }
+
+    public String getTitle() {
+        return title;
+    }
+
+    public void setTitle(String title) {
+        this.title = title;
+    }
+
+    public Integer getCategoryId() {
+        return categoryId;
+    }
+
+    public void setCategoryId(Integer categoryId) {
+        this.categoryId = categoryId;
+    }
+
+    public Integer getAreaId() {
+        return areaId;
+    }
+
+    public void setAreaId(Integer areaId) {
+        this.areaId = areaId;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+
+    public Integer getTagId() {
+        return tagId;
+    }
+
+    public void setTagId(Integer tagId) {
+        this.tagId = tagId;
+    }
+
+    public Long getPublisherId() {
+        return publisherId;
+    }
+
+    public void setPublisherId(Long publisherId) {
+        this.publisherId = publisherId;
+    }
+}

+ 183 - 0
src/main/java/com/zhentao/baoming/dto/order/OrderSignupDTO.java

@@ -0,0 +1,183 @@
+package com.zhentao.baoming.dto.order;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 点单报名DTO
+ */
+public class OrderSignupDTO implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 报名ID
+     */
+    private Integer id;
+
+    /**
+     * 点单活动ID
+     */
+    private Integer orderId;
+
+    /**
+     * 活动标题
+     */
+    private String orderTitle;
+
+    /**
+     * 活动封面图片
+     */
+    private String orderCoverImage;
+
+    /**
+     * 报名用户ID
+     */
+    private Long userId;
+
+    /**
+     * 用户名
+     */
+    private String username;
+
+    /**
+     * 真实姓名
+     */
+    private String realName;
+
+    /**
+     * 用户头像
+     */
+    private String avatar;
+
+    /**
+     * 状态:0-已报名,1-已通过,2-已拒绝,3-已取消,4-已完成
+     */
+    private Integer status;
+    /**
+     * 连接状态:0-未连接,1-已连接
+     */
+    private Integer connect;
+
+    /**
+     * 活动时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date activityTime;
+
+    /**
+     * 备注信息
+     */
+    private String comment;
+
+    /**
+     * 报名时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date signupTime;
+
+    public Integer getId() {
+        return id;
+    }
+
+    public void setId(Integer id) {
+        this.id = id;
+    }
+
+    public Integer getOrderId() {
+        return orderId;
+    }
+
+    public void setOrderId(Integer orderId) {
+        this.orderId = orderId;
+    }
+
+    public String getOrderTitle() {
+        return orderTitle;
+    }
+
+    public void setOrderTitle(String orderTitle) {
+        this.orderTitle = orderTitle;
+    }
+
+    public String getOrderCoverImage() {
+        return orderCoverImage;
+    }
+
+    public void setOrderCoverImage(String orderCoverImage) {
+        this.orderCoverImage = orderCoverImage;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public String getUsername() {
+        return username;
+    }
+
+    public void setUsername(String username) {
+        this.username = username;
+    }
+
+    public String getRealName() {
+        return realName;
+    }
+
+    public void setRealName(String realName) {
+        this.realName = realName;
+    }
+
+    public String getAvatar() {
+        return avatar;
+    }
+
+    public void setAvatar(String avatar) {
+        this.avatar = avatar;
+    }
+
+    public Integer getStatus() {
+        return status;
+    }
+
+    public void setStatus(Integer status) {
+        this.status = status;
+    }
+    public Integer getConnect() {
+        return connect;
+    }
+    public void setConnect(Integer connect) {}
+
+    public Date getActivityTime() {
+        return activityTime;
+    }
+
+    public void setActivityTime(Date activityTime) {
+        this.activityTime = activityTime;
+    }
+
+    public String getComment() {
+        return comment;
+    }
+
+    public void setComment(String comment) {
+        this.comment = comment;
+    }
+
+    public Date getSignupTime() {
+        return signupTime;
+    }
+
+    public void setSignupTime(Date signupTime) {
+        this.signupTime = signupTime;
+    }
+}

+ 18 - 0
src/main/java/com/zhentao/baoming/mapper/ActivityTagRelationMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.ActivityTagRelation;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【activity_tag_relation(活动标签关联表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.ActivityTagRelation
+*/
+public interface ActivityTagRelationMapper extends BaseMapper<ActivityTagRelation> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/baoming/mapper/AreaMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.Area;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【area(区域表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.Area
+*/
+public interface AreaMapper extends BaseMapper<Area> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/baoming/mapper/EvaluationMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.Evaluation;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【evaluation(评价表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.Evaluation
+*/
+public interface EvaluationMapper extends BaseMapper<Evaluation> {
+
+}
+
+
+
+

+ 28 - 0
src/main/java/com/zhentao/baoming/mapper/OrderActivityMapper.java

@@ -0,0 +1,28 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.OrderActivity;
+import org.apache.ibatis.annotations.Select;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_activity(点单活动表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.OrderActivity
+*/
+public interface OrderActivityMapper extends BaseMapper<OrderActivity> {
+    /**
+     * 直接查询活动的报名人数
+     */
+    @Select("SELECT id, title, current_number, " +
+            "(SELECT COUNT(*) FROM order_signup WHERE order_id = order_activity.id AND status IN (0,1)) as actual_signups " +
+            "FROM order_activity")
+    List<Map<String, Object>> checkSignupNumbers();
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/baoming/mapper/OrderSignupMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.OrderSignup;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_signup(点单报名表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.OrderSignup
+*/
+public interface OrderSignupMapper extends BaseMapper<OrderSignup> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/baoming/mapper/OrderTagMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.OrderTag;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_tag(点单标签表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.OrderTag
+*/
+public interface OrderTagMapper extends BaseMapper<OrderTag> {
+
+}
+
+
+
+

+ 18 - 0
src/main/java/com/zhentao/baoming/mapper/ServiceCategoryMapper.java

@@ -0,0 +1,18 @@
+package com.zhentao.baoming.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.zhentao.baoming.pojo.ServiceCategory;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【service_category(服务类别表)】的数据库操作Mapper
+* @createDate 2025-07-01 14:17:25
+* @Entity com.zhentao.pojo.ServiceCategory
+*/
+public interface ServiceCategoryMapper extends BaseMapper<ServiceCategory> {
+
+}
+
+
+
+

+ 77 - 0
src/main/java/com/zhentao/baoming/pojo/ActivityTagRelation.java

@@ -0,0 +1,77 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 活动标签关联表
+ * @TableName activity_tag_relation
+ */
+@TableName(value ="activity_tag_relation")
+@Data
+public class ActivityTagRelation implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 活动ID
+     */
+    private Integer activityId;
+
+    /**
+     * 标签ID
+     */
+    private Integer tagId;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        ActivityTagRelation other = (ActivityTagRelation) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getActivityId() == null ? other.getActivityId() == null : this.getActivityId().equals(other.getActivityId()))
+            && (this.getTagId() == null ? other.getTagId() == null : this.getTagId().equals(other.getTagId()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getActivityId() == null) ? 0 : getActivityId().hashCode());
+        result = prime * result + ((getTagId() == null) ? 0 : getTagId().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", activityId=").append(activityId);
+        sb.append(", tagId=").append(tagId);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 92 - 0
src/main/java/com/zhentao/baoming/pojo/Area.java

@@ -0,0 +1,92 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 区域表
+ * @TableName area
+ */
+@TableName(value ="area")
+@Data
+public class Area implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 区域名称
+     */
+    private String name;
+
+    /**
+     * 父级区域ID
+     */
+    private Integer parentId;
+
+    /**
+     * 区域级别:0-省,1-市,2-区县
+     */
+    private Integer level;
+
+    /**
+     * 子区域列表(非数据库字段)
+     */
+    @TableField(exist = false)
+    private List<Area> children;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Area other = (Area) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
+            && (this.getParentId() == null ? other.getParentId() == null : this.getParentId().equals(other.getParentId()))
+            && (this.getLevel() == null ? other.getLevel() == null : this.getLevel().equals(other.getLevel()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
+        result = prime * result + ((getParentId() == null) ? 0 : getParentId().hashCode());
+        result = prime * result + ((getLevel() == null) ? 0 : getLevel().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", name=").append(name);
+        sb.append(", parentId=").append(parentId);
+        sb.append(", level=").append(level);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 114 - 0
src/main/java/com/zhentao/baoming/pojo/Evaluation.java

@@ -0,0 +1,114 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 评价表
+ * @TableName evaluation
+ */
+@TableName(value ="evaluation")
+@Data
+public class Evaluation implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 点单活动ID
+     */
+    private Integer orderId;
+
+    /**
+     * 评价用户ID
+     */
+    private Long userId;
+
+    /**
+     * 被评价对象ID(可能是用户或活动)
+     */
+    private Integer targetId;
+
+    /**
+     * 评分(1-5)
+     */
+    private Integer rating;
+
+    /**
+     * 评价内容
+     */
+    private String content;
+
+    /**
+     *
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        Evaluation other = (Evaluation) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getOrderId() == null ? other.getOrderId() == null : this.getOrderId().equals(other.getOrderId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getTargetId() == null ? other.getTargetId() == null : this.getTargetId().equals(other.getTargetId()))
+            && (this.getRating() == null ? other.getRating() == null : this.getRating().equals(other.getRating()))
+            && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getOrderId() == null) ? 0 : getOrderId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getTargetId() == null) ? 0 : getTargetId().hashCode());
+        result = prime * result + ((getRating() == null) ? 0 : getRating().hashCode());
+        result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", orderId=").append(orderId);
+        sb.append(", userId=").append(userId);
+        sb.append(", targetId=").append(targetId);
+        sb.append(", rating=").append(rating);
+        sb.append(", content=").append(content);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 209 - 0
src/main/java/com/zhentao/baoming/pojo/OrderActivity.java

@@ -0,0 +1,209 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 点单活动表
+ * @TableName order_activity
+ */
+@TableName(value ="order_activity")
+@Data
+public class OrderActivity implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 活动标题
+     */
+    private String title;
+
+    /**
+     * 发布者ID
+     */
+    private Long publisherId;
+
+    /**
+     * 服务类别ID
+     */
+    private Integer categoryId;
+
+    /**
+     * 项目详情
+     */
+    private String content;
+
+    /**
+     * 区域ID
+     */
+    private Integer areaId;
+
+    /**
+     * 详细地址
+     */
+    private String address;
+
+    /**
+     * 活动时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date activityTime;
+
+    /**
+     * 招募开始时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date recruitStart;
+
+    /**
+     * 招募结束时间
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date recruitEnd;
+
+    /**
+     * 招募人数
+     */
+    private Integer recruitNumber;
+
+    /**
+     * 当前报名人数
+     */
+    private Integer currentNumber;
+
+    /**
+     * 状态:0-待接单,1-招募中,2-已接单,3-已完成,4-已取消
+     */
+    private Integer status;
+
+    /**
+     * 接单状态:0-待接单,1-已接单,2-已完成,3-已取消
+     */
+    private Integer connect;
+    /**
+     * 报名状态:0-待报名,1-已报名,2-已取消
+     */
+    private Integer enrollmentStatus;
+
+    /**
+     * 封面图片
+     */
+    private String coverImage;
+
+    /**
+     *
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date createTime;
+
+    /**
+     *
+     */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    private Date updateTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        OrderActivity other = (OrderActivity) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getTitle() == null ? other.getTitle() == null : this.getTitle().equals(other.getTitle()))
+            && (this.getPublisherId() == null ? other.getPublisherId() == null : this.getPublisherId().equals(other.getPublisherId()))
+            && (this.getCategoryId() == null ? other.getCategoryId() == null : this.getCategoryId().equals(other.getCategoryId()))
+            && (this.getContent() == null ? other.getContent() == null : this.getContent().equals(other.getContent()))
+            && (this.getAreaId() == null ? other.getAreaId() == null : this.getAreaId().equals(other.getAreaId()))
+            && (this.getAddress() == null ? other.getAddress() == null : this.getAddress().equals(other.getAddress()))
+            && (this.getActivityTime() == null ? other.getActivityTime() == null : this.getActivityTime().equals(other.getActivityTime()))
+            && (this.getRecruitStart() == null ? other.getRecruitStart() == null : this.getRecruitStart().equals(other.getRecruitStart()))
+            && (this.getRecruitEnd() == null ? other.getRecruitEnd() == null : this.getRecruitEnd().equals(other.getRecruitEnd()))
+            && (this.getRecruitNumber() == null ? other.getRecruitNumber() == null : this.getRecruitNumber().equals(other.getRecruitNumber()))
+            && (this.getCurrentNumber() == null ? other.getCurrentNumber() == null : this.getCurrentNumber().equals(other.getCurrentNumber()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+                && (this.getEnrollmentStatus() == null ? other.getEnrollmentStatus() == null : this.getEnrollmentStatus().equals(other.getEnrollmentStatus()))
+                && (this.getConnect() == null ? other.getConnect() == null : this.getConnect().equals(other.getConnect()))
+            && (this.getCoverImage() == null ? other.getCoverImage() == null : this.getCoverImage().equals(other.getCoverImage()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()))
+            && (this.getUpdateTime() == null ? other.getUpdateTime() == null : this.getUpdateTime().equals(other.getUpdateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getTitle() == null) ? 0 : getTitle().hashCode());
+        result = prime * result + ((getPublisherId() == null) ? 0 : getPublisherId().hashCode());
+        result = prime * result + ((getCategoryId() == null) ? 0 : getCategoryId().hashCode());
+        result = prime * result + ((getContent() == null) ? 0 : getContent().hashCode());
+        result = prime * result + ((getAreaId() == null) ? 0 : getAreaId().hashCode());
+        result = prime * result + ((getAddress() == null) ? 0 : getAddress().hashCode());
+        result = prime * result + ((getActivityTime() == null) ? 0 : getActivityTime().hashCode());
+        result = prime * result + ((getRecruitStart() == null) ? 0 : getRecruitStart().hashCode());
+        result = prime * result + ((getRecruitEnd() == null) ? 0 : getRecruitEnd().hashCode());
+        result = prime * result + ((getRecruitNumber() == null) ? 0 : getRecruitNumber().hashCode());
+        result = prime * result + ((getCurrentNumber() == null) ? 0 : getCurrentNumber().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getEnrollmentStatus() == null) ? 0 : getEnrollmentStatus().hashCode());
+        result = prime * result + ((getConnect() == null) ? 0 : getConnect().hashCode());
+        result = prime * result + ((getCoverImage() == null) ? 0 : getCoverImage().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        result = prime * result + ((getUpdateTime() == null) ? 0 : getUpdateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", title=").append(title);
+        sb.append(", publisherId=").append(publisherId);
+        sb.append(", categoryId=").append(categoryId);
+        sb.append(", content=").append(content);
+        sb.append(", areaId=").append(areaId);
+        sb.append(", address=").append(address);
+        sb.append(", activityTime=").append(activityTime);
+        sb.append(", recruitStart=").append(recruitStart);
+        sb.append(", recruitEnd=").append(recruitEnd);
+        sb.append(", recruitNumber=").append(recruitNumber);
+        sb.append(", currentNumber=").append(currentNumber);
+        sb.append(", status=").append(status);
+        sb.append(", enrollmentStatus=").append(enrollmentStatus);
+        sb.append(", connect=").append(connect);
+        sb.append(", coverImage=").append(coverImage);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", updateTime=").append(updateTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 93 - 0
src/main/java/com/zhentao/baoming/pojo/OrderSignup.java

@@ -0,0 +1,93 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 点单报名表
+ * @TableName order_signup
+ */
+@TableName(value ="order_signup")
+@Data
+public class OrderSignup implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 点单活动ID
+     */
+    private Integer orderId;
+
+    /**
+     * 报名用户ID
+     */
+    private Long userId;
+
+    /**
+     * 状态:0-已报名,1-已通过,2-已拒绝,3-已取消,4-已完成
+     */
+    private Integer status;
+
+    /**
+     * 备注信息
+     */
+    private String comment;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        OrderSignup other = (OrderSignup) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getOrderId() == null ? other.getOrderId() == null : this.getOrderId().equals(other.getOrderId()))
+            && (this.getUserId() == null ? other.getUserId() == null : this.getUserId().equals(other.getUserId()))
+            && (this.getStatus() == null ? other.getStatus() == null : this.getStatus().equals(other.getStatus()))
+            && (this.getComment() == null ? other.getComment() == null : this.getComment().equals(other.getComment()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getOrderId() == null) ? 0 : getOrderId().hashCode());
+        result = prime * result + ((getUserId() == null) ? 0 : getUserId().hashCode());
+        result = prime * result + ((getStatus() == null) ? 0 : getStatus().hashCode());
+        result = prime * result + ((getComment() == null) ? 0 : getComment().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", orderId=").append(orderId);
+        sb.append(", userId=").append(userId);
+        sb.append(", status=").append(status);
+        sb.append(", comment=").append(comment);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 78 - 0
src/main/java/com/zhentao/baoming/pojo/OrderTag.java

@@ -0,0 +1,78 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 点单标签表
+ * @TableName order_tag
+ */
+@TableName(value ="order_tag")
+@Data
+public class OrderTag implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 标签名称
+     */
+    private String name;
+
+    /**
+     *
+     */
+    private Date createTime;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        OrderTag other = (OrderTag) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
+            && (this.getCreateTime() == null ? other.getCreateTime() == null : this.getCreateTime().equals(other.getCreateTime()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
+        result = prime * result + ((getCreateTime() == null) ? 0 : getCreateTime().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", name=").append(name);
+        sb.append(", createTime=").append(createTime);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 85 - 0
src/main/java/com/zhentao/baoming/pojo/ServiceCategory.java

@@ -0,0 +1,85 @@
+package com.zhentao.baoming.pojo;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+
+/**
+ * 服务类别表
+ * @TableName service_category
+ */
+@TableName(value ="service_category")
+@Data
+public class ServiceCategory implements Serializable {
+    /**
+     *
+     */
+    @TableId(type = IdType.AUTO)
+    private Integer id;
+
+    /**
+     * 类别名称(社区服务、教育培训、法律咨询等)
+     */
+    private String name;
+
+    /**
+     * 图标路径
+     */
+    private String icon;
+
+    /**
+     * 排序顺序
+     */
+    private Integer sortOrder;
+
+    @TableField(exist = false)
+    private static final long serialVersionUID = 1L;
+
+    @Override
+    public boolean equals(Object that) {
+        if (this == that) {
+            return true;
+        }
+        if (that == null) {
+            return false;
+        }
+        if (getClass() != that.getClass()) {
+            return false;
+        }
+        ServiceCategory other = (ServiceCategory) that;
+        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
+            && (this.getName() == null ? other.getName() == null : this.getName().equals(other.getName()))
+            && (this.getIcon() == null ? other.getIcon() == null : this.getIcon().equals(other.getIcon()))
+            && (this.getSortOrder() == null ? other.getSortOrder() == null : this.getSortOrder().equals(other.getSortOrder()));
+    }
+
+    @Override
+    public int hashCode() {
+        final int prime = 31;
+        int result = 1;
+        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
+        result = prime * result + ((getName() == null) ? 0 : getName().hashCode());
+        result = prime * result + ((getIcon() == null) ? 0 : getIcon().hashCode());
+        result = prime * result + ((getSortOrder() == null) ? 0 : getSortOrder().hashCode());
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append(getClass().getSimpleName());
+        sb.append(" [");
+        sb.append("Hash = ").append(hashCode());
+        sb.append(", id=").append(id);
+        sb.append(", name=").append(name);
+        sb.append(", icon=").append(icon);
+        sb.append(", sortOrder=").append(sortOrder);
+        sb.append(", serialVersionUID=").append(serialVersionUID);
+        sb.append("]");
+        return sb.toString();
+    }
+}

+ 30 - 0
src/main/java/com/zhentao/baoming/service/ActivityTagRelationService.java

@@ -0,0 +1,30 @@
+package com.zhentao.baoming.service;
+
+
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.pojo.ActivityTagRelation;
+
+import java.util.List;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【activity_tag_relation(活动标签关联表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface ActivityTagRelationService extends IService<ActivityTagRelation> {
+    /**
+     * 根据活动ID获取标签ID列表
+     * @param activityId 活动ID
+     * @return 标签ID列表
+     */
+    List<Integer> getTagIdsByActivityId(Integer activityId);
+    
+    /**
+     * 保存活动的标签关联关系
+     * @param activityId 活动ID
+     * @param tagIds 标签ID列表
+     * @return 是否保存成功
+     */
+    boolean saveBatchByActivityId(Integer activityId, List<Integer> tagIds);
+}

+ 26 - 0
src/main/java/com/zhentao/baoming/service/AreaService.java

@@ -0,0 +1,26 @@
+package com.zhentao.baoming.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.pojo.Area;
+
+import java.util.List;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【area(区域表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface AreaService extends IService<Area> {
+    /**
+     * 获取区域路径(省市区)
+     * @param areaId 区域ID
+     * @return 路径列表,顺序为:省、市、区
+     */
+    List<Area> getAreaPath(Integer areaId);
+
+    /**
+     * 获取区域树结构
+     * @return 区域树
+     */
+    List<Area> getAreaTree();
+}

+ 13 - 0
src/main/java/com/zhentao/baoming/service/EvaluationService.java

@@ -0,0 +1,13 @@
+package com.zhentao.baoming.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.pojo.Evaluation;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【evaluation(评价表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface EvaluationService extends IService<Evaluation> {
+
+}

+ 58 - 0
src/main/java/com/zhentao/baoming/service/OrderActivityService.java

@@ -0,0 +1,58 @@
+package com.zhentao.baoming.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.dto.common.PageResult;
+import com.zhentao.baoming.dto.order.OrderActivityDTO;
+import com.zhentao.baoming.dto.order.OrderActivityDetailDTO;
+import com.zhentao.baoming.dto.order.OrderActivityQueryDTO;
+import com.zhentao.baoming.pojo.OrderActivity;
+
+import java.util.Map;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_activity(点单活动表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface OrderActivityService extends IService<OrderActivity> {
+    /**
+     * 获取活动详情
+     * @param activityId 活动ID
+     * @return 活动详情DTO
+     */
+    OrderActivityDetailDTO getActivityDetail(Integer activityId);
+    
+    /**
+     * 查询活动列表
+     * @param queryDTO 查询条件
+     * @return 分页活动列表
+     */
+    PageResult<OrderActivityDTO> queryActivityList(OrderActivityQueryDTO queryDTO);
+
+    /**
+     * 创建新活动
+     * @param activityDTO 活动信息
+     * @return 创建的活动实体
+     */
+    OrderActivity createActivity(OrderActivityDTO activityDTO);
+
+    /**
+     * 修复所有活动的报名人数
+     * 这个方法应该在管理员后台调用,不要在普通接口中使用
+     */
+    void fixAllActivitySignupNumbers();
+
+    /**
+     * 诊断活动报名人数
+     * @param activityId 活动ID
+     * @return 诊断结果
+     */
+    Map<String, Object> diagnoseSignupNumbers(Integer activityId);
+
+    /**
+     * 修复活动报名人数
+     * @param activityId 活动ID
+     * @return 是否修复成功
+     */
+    boolean fixSignupNumbers(Integer activityId);
+}

+ 77 - 0
src/main/java/com/zhentao/baoming/service/OrderSignupService.java

@@ -0,0 +1,77 @@
+package com.zhentao.baoming.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.dto.order.OrderSignupDTO;
+import com.zhentao.baoming.pojo.OrderSignup;
+
+import java.util.List;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_signup(点单报名表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface OrderSignupService extends IService<OrderSignup> {
+    /**
+     * 获取用户的报名列表
+     * @param userId 用户ID
+     * @return 报名DTO列表
+     */
+    List<OrderSignupDTO> getUserSignupList(Long userId);
+
+    /**
+     * 获取活动的报名列表
+     * @param activityId 活动ID
+     * @return 报名DTO列表
+     */
+    List<OrderSignupDTO> getActivitySignupList(Integer activityId);
+    
+    /**
+     * 用户报名活动
+     * @param userId 用户ID
+     * @param activityId 活动ID
+     * @param comment 报名备注
+     * @return 是否报名成功
+     */
+    boolean signupActivity(Long userId, Integer activityId, String comment);
+    
+    /**
+     * 用户取消报名
+     * @param userId 用户ID
+     * @param activityId 活动ID
+     * @return 是否取消成功
+     */
+    boolean cancelSignup(Long userId, Integer activityId);
+    
+    /**
+     * 获取活动的当前报名人数
+     * @param activityId 活动ID
+     * @return 当前报名人数
+     */
+    int getActivitySignupCount(Integer activityId);
+
+    /**
+     * 更新报名状态
+     * @param userId 用户ID
+     * @param activityId 活动ID
+     * @param status 新状态
+     * @return 是否更新成功
+     */
+    boolean updateSignupStatus(Long userId, Integer activityId, Integer status);
+
+    /**
+     * 审核通过报名
+     * @param userId 用户ID
+     * @param activityId 活动ID
+     * @return 是否审核成功
+     */
+    boolean approveSignup(Long userId, Integer activityId);
+
+    /**
+     * 拒绝报名
+     * @param userId 用户ID
+     * @param activityId 活动ID
+     * @return 是否拒绝成功
+     */
+    boolean rejectSignup(Long userId, Integer activityId);
+}

+ 20 - 0
src/main/java/com/zhentao/baoming/service/OrderTagService.java

@@ -0,0 +1,20 @@
+package com.zhentao.baoming.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.pojo.OrderTag;
+
+import java.util.List;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_tag(点单标签表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface OrderTagService extends IService<OrderTag> {
+    /**
+     * 根据活动ID获取标签列表
+     * @param activityId 活动ID
+     * @return 标签列表
+     */
+    List<OrderTag> getTagsByActivityId(Integer activityId);
+}

+ 13 - 0
src/main/java/com/zhentao/baoming/service/ServiceCategoryService.java

@@ -0,0 +1,13 @@
+package com.zhentao.baoming.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.zhentao.baoming.pojo.ServiceCategory;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【service_category(服务类别表)】的数据库操作Service
+* @createDate 2025-07-01 14:17:25
+*/
+public interface ServiceCategoryService extends IService<ServiceCategory> {
+
+}

+ 68 - 0
src/main/java/com/zhentao/baoming/service/impl/ActivityTagRelationServiceImpl.java

@@ -0,0 +1,68 @@
+package com.zhentao.baoming.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.mapper.ActivityTagRelationMapper;
+import com.zhentao.baoming.pojo.ActivityTagRelation;
+import com.zhentao.baoming.service.ActivityTagRelationService;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【activity_tag_relation(活动标签关联表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+public class ActivityTagRelationServiceImpl extends ServiceImpl<ActivityTagRelationMapper, ActivityTagRelation>
+    implements ActivityTagRelationService {
+
+    @Override
+    public List<Integer> getTagIdsByActivityId(Integer activityId) {
+        LambdaQueryWrapper<ActivityTagRelation> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(ActivityTagRelation::getActivityId, activityId);
+        List<ActivityTagRelation> relations = list(queryWrapper);
+
+        if (relations != null && !relations.isEmpty()) {
+            return relations.stream()
+                    .map(ActivityTagRelation::getTagId)
+                    .collect(Collectors.toList());
+        }
+        return new ArrayList<>();
+    }
+    
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public boolean saveBatchByActivityId(Integer activityId, List<Integer> tagIds) {
+        try {
+            // 先删除原有关联
+            LambdaQueryWrapper<ActivityTagRelation> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(ActivityTagRelation::getActivityId, activityId);
+            remove(queryWrapper);
+            
+            // 创建新的关联
+            List<ActivityTagRelation> relations = new ArrayList<>();
+            for (Integer tagId : tagIds) {
+                ActivityTagRelation relation = new ActivityTagRelation();
+                relation.setActivityId(activityId);
+                relation.setTagId(tagId);
+                relations.add(relation);
+            }
+            
+            // 批量保存
+            return saveBatch(relations);
+        } catch (Exception e) {
+            // 记录异常并返回失败
+            e.printStackTrace();
+            return false;
+        }
+    }
+}
+
+
+
+

+ 92 - 0
src/main/java/com/zhentao/baoming/service/impl/AreaServiceImpl.java

@@ -0,0 +1,92 @@
+package com.zhentao.baoming.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.mapper.AreaMapper;
+import com.zhentao.baoming.pojo.Area;
+import com.zhentao.baoming.service.AreaService;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【area(区域表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+public class AreaServiceImpl extends ServiceImpl<AreaMapper, Area>
+    implements AreaService {
+
+    @Override
+    public List<Area> getAreaPath(Integer areaId) {
+        List<Area> path = new ArrayList<>();
+
+        // 查询当前区域
+        Area area = getById(areaId);
+        if (area == null) {
+            return path;
+        }
+
+        path.add(area);
+
+        // 查询父级区域,直到找到省级区域
+        Integer parentId = area.getParentId();
+        while (parentId != null && parentId > 0) {
+            Area parent = getById(parentId);
+            if (parent == null) {
+                break;
+            }
+            path.add(0, parent);  // 添加到路径的前面
+            parentId = parent.getParentId();
+        }
+
+        return path;
+    }
+
+    @Override
+    public List<Area> getAreaTree() {
+        // 查询所有区域
+        List<Area> allAreas = list();
+        if (allAreas == null || allAreas.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 构建树形结构
+        // 1. 先找出所有省级区域(parentId为0或null)
+        List<Area> provinces = allAreas.stream()
+                .filter(a -> a.getLevel() != null && a.getLevel() == 0)
+                .collect(Collectors.toList());
+
+        // 2. 将所有区域按parentId分组
+        Map<Integer, List<Area>> areaMap = allAreas.stream()
+                .collect(Collectors.groupingBy(Area::getParentId));
+
+        // 3. 递归设置子区域
+        setChildren(provinces, areaMap);
+
+        return provinces;
+    }
+
+    /**
+     * 递归设置子区域
+     */
+    private void setChildren(List<Area> areas, Map<Integer, List<Area>> areaMap) {
+        for (Area area : areas) {
+            // 查找子区域
+            List<Area> children = areaMap.get(area.getId());
+            if (children != null && !children.isEmpty()) {
+                // 设置子区域
+                area.setChildren(children);
+                // 递归设置子区域的子区域
+                setChildren(children, areaMap);
+            }
+        }
+    }
+}
+
+
+
+

+ 22 - 0
src/main/java/com/zhentao/baoming/service/impl/EvaluationServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.baoming.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.mapper.EvaluationMapper;
+import com.zhentao.baoming.pojo.Evaluation;
+import com.zhentao.baoming.service.EvaluationService;
+import org.springframework.stereotype.Service;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【evaluation(评价表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+public class EvaluationServiceImpl extends ServiceImpl<EvaluationMapper, Evaluation>
+    implements EvaluationService {
+
+}
+
+
+
+

+ 617 - 0
src/main/java/com/zhentao/baoming/service/impl/OrderActivityServiceImpl.java

@@ -0,0 +1,617 @@
+package com.zhentao.baoming.service.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.dto.common.PageResult;
+import com.zhentao.baoming.dto.order.OrderActivityDTO;
+import com.zhentao.baoming.dto.order.OrderActivityDetailDTO;
+import com.zhentao.baoming.dto.order.OrderActivityQueryDTO;
+import com.zhentao.baoming.mapper.AreaMapper;
+import com.zhentao.baoming.mapper.OrderActivityMapper;
+import com.zhentao.baoming.mapper.OrderSignupMapper;
+import com.zhentao.baoming.mapper.ServiceCategoryMapper;
+import com.zhentao.baoming.pojo.*;
+import com.zhentao.baoming.service.ActivityTagRelationService;
+import com.zhentao.baoming.service.OrderActivityService;
+import com.zhentao.baoming.service.OrderTagService;
+import com.zhentao.common.utils.TokenUtils;
+import com.zhentao.user.domain.User;
+import com.zhentao.user.mapper.UserMapper;
+import org.redisson.api.RLock;
+import org.redisson.api.RedissonClient;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_activity(点单活动表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+public class OrderActivityServiceImpl extends ServiceImpl<OrderActivityMapper, OrderActivity>
+    implements OrderActivityService {
+
+    private static final Logger log = LoggerFactory.getLogger(OrderActivityServiceImpl.class);
+
+    @Autowired
+    private ActivityTagRelationService activityTagRelationService;
+
+    @Autowired
+    private OrderTagService orderTagService;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private ServiceCategoryMapper serviceCategoryMapper;
+
+    @Autowired
+    private AreaMapper areaMapper;
+
+    @Autowired
+    private OrderSignupMapper orderSignupMapper;
+
+    @Autowired
+    private StringRedisTemplate stringRedisTemplate;
+
+    @Autowired
+    private RedissonClient redissonClient;
+    @Autowired
+    private OrderActivityMapper orderActivityMapper;
+    private static final String ACTIVITY_DETAIL_KEY_PREFIX = "activity:detail:";
+    private static final String ACTIVITY_DETAIL_LOCK_PREFIX = "activity:detail:lock:";
+    private static final long CACHE_EXPIRE_TIME = 30; // 缓存时间增加到30分钟
+    private static final long LOCK_WAIT_TIME = 5; // 锁等待增加到5秒
+    private static final long LOCK_LEASE_TIME = 10; // 锁租约增加到10秒
+
+    // 添加活动列表缓存key前缀
+    private static final String ACTIVITY_LIST_KEY_PREFIX = "activity:list:";
+    private static final String ACTIVITY_LIST_LOCK_PREFIX = "activity:list:lock:";
+
+    // OrderSignup的状态常量
+    private static final int SIGNUP_STATUS_APPLIED = 0;    // 已报名(待审核)
+    private static final int SIGNUP_STATUS_APPROVED = 1;   // 已通过
+    private static final int SIGNUP_STATUS_REJECTED = 2;   // 已拒绝
+    private static final int SIGNUP_STATUS_CANCELED = 3;   // 已取消
+
+    @Override
+    public OrderActivityDetailDTO getActivityDetail(Integer activityId) {
+        if (activityId == null) {
+            return null;
+        }
+
+        // 获取当前登录用户ID
+        Long currentUserId = null;
+        try {
+            String token = TokenUtils.getTokenFromRequest();
+            if (token != null) {
+                String userIdStr = TokenUtils.getUserIdFromToken(token);
+                if (userIdStr != null) {
+                    currentUserId = Long.valueOf(userIdStr);
+                }
+            }
+        } catch (Exception e) {
+            // 用户未登录或token无效,忽略异常
+        }
+
+        // 缓存key
+        String cacheKey = ACTIVITY_DETAIL_KEY_PREFIX + activityId;
+        String userSpecificCacheKey = currentUserId != null ? cacheKey + ":" + currentUserId : cacheKey;
+
+        // 先从缓存获取,使用try-catch包裹Redis操作以避免异常影响正常流程
+        try {
+            String cacheValue = stringRedisTemplate.opsForValue().get(userSpecificCacheKey);
+            if (cacheValue != null && !cacheValue.isEmpty()) {
+                try {
+                    return JSON.parseObject(cacheValue, OrderActivityDetailDTO.class);
+                } catch (Exception e) {
+                    log.warn("解析缓存数据失败,将重新查询数据库. key={}", userSpecificCacheKey, e);
+                    try {
+                        stringRedisTemplate.delete(userSpecificCacheKey);
+                    } catch (Exception ex) {
+                        log.warn("删除失效缓存失败", ex);
+                    }
+                }
+            }
+        } catch (Exception e) {
+            // Redis操作异常,直接从数据库查询
+            log.warn("Redis操作异常,将直接查询数据库", e);
+            return queryActivityDetail(activityId, currentUserId);
+        }
+
+        // 使用分布式锁,避免缓存击穿
+        String lockKey = ACTIVITY_DETAIL_LOCK_PREFIX + activityId;
+        RLock lock = redissonClient.getLock(lockKey);
+        boolean locked = false;
+
+        try {
+            // 尝试获取锁,但不要一直等待
+            locked = lock.tryLock(LOCK_WAIT_TIME, LOCK_LEASE_TIME, TimeUnit.SECONDS);
+            if (!locked) {
+                // 获取锁失败,直接查询数据库
+                log.warn("获取分布式锁超时,将直接查询数据库. lockKey={}", lockKey);
+                return queryActivityDetail(activityId, currentUserId);
+            }
+
+            // 双重检查
+            try {
+                String cacheValue = stringRedisTemplate.opsForValue().get(userSpecificCacheKey);
+                if (cacheValue != null && !cacheValue.isEmpty()) {
+                    try {
+                        return JSON.parseObject(cacheValue, OrderActivityDetailDTO.class);
+                    } catch (Exception e) {
+                        log.warn("解析缓存数据失败. key={}", userSpecificCacheKey, e);
+                        try {
+                            stringRedisTemplate.delete(userSpecificCacheKey);
+                        } catch (Exception ex) {
+                            log.warn("删除失效缓存失败", ex);
+                        }
+                    }
+                }
+            } catch (Exception e) {
+                log.warn("Redis操作异常,将直接查询数据库", e);
+                return queryActivityDetail(activityId, currentUserId);
+            }
+
+            // 查询数据库
+            OrderActivityDetailDTO detailDTO = queryActivityDetail(activityId, currentUserId);
+
+            // 存入缓存,使用try-catch避免异常
+            if (detailDTO != null) {
+                try {
+                    stringRedisTemplate.opsForValue().set(userSpecificCacheKey, JSON.toJSONString(detailDTO), CACHE_EXPIRE_TIME, TimeUnit.MINUTES);
+                } catch (Exception e) {
+                    log.warn("将数据存入Redis缓存失败,但不影响返回结果", e);
+                }
+            }
+
+            return detailDTO;
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            return queryActivityDetail(activityId, currentUserId);
+        } catch (Exception e) {
+            // 处理其他异常
+            log.error("获取活动详情异常", e);
+            return queryActivityDetail(activityId, currentUserId);
+        } finally {
+            // 只有当前线程持有锁时才释放
+            if (locked && lock.isHeldByCurrentThread()) {
+                try {
+                    lock.unlock();
+                } catch (Exception e) {
+                    log.warn("释放锁失败", e);
+                }
+            }
+        }
+    }
+
+    private OrderActivityDetailDTO queryActivityDetail(Integer activityId, Long currentUserId) {
+        // 查询活动基本信息
+        OrderActivity activity = getById(activityId);
+        System.out.println(111);
+        if (activity == null) {
+            return null;
+        }
+
+        // 转换为DTO
+        OrderActivityDetailDTO detailDTO = new OrderActivityDetailDTO();
+        BeanUtils.copyProperties(activity, detailDTO);
+
+        // 确保招募人数不为null
+        if (detailDTO.getRecruitNumber() == null) {
+            detailDTO.setRecruitNumber(20);
+        }
+
+        // 检查当前用户是否已报名
+        if (currentUserId != null) {
+            LambdaQueryWrapper<OrderSignup> userSignupQuery = new LambdaQueryWrapper<>();
+            userSignupQuery.eq(OrderSignup::getOrderId, activityId)
+                    .eq(OrderSignup::getUserId, currentUserId)
+                    .in(OrderSignup::getStatus, SIGNUP_STATUS_APPLIED, SIGNUP_STATUS_APPROVED);
+            int count = Math.toIntExact(orderSignupMapper.selectCount(userSignupQuery));
+            detailDTO.setIsSignedUp(count > 0);
+            log.info("活动ID: {}, 用户ID: {}, 是否已报名: {}", activityId, currentUserId, count > 0);
+        } else {
+            detailDTO.setIsSignedUp(false);
+        }
+
+        // 查询发布者信息
+        User publisher = userMapper.selectById(activity.getPublisherId());
+        if (publisher != null) {
+            detailDTO.setPublisherName(publisher.getRealName());
+            detailDTO.setPublisherAvatar(publisher.getAvatar());
+            detailDTO.setPublisherRating(publisher.getRating() != null ? publisher.getRating().intValue() : 0);
+        }
+
+        // 查询服务类别信息
+        if (activity.getCategoryId() != null) {
+            ServiceCategory category = serviceCategoryMapper.selectById(activity.getCategoryId());
+            if (category != null) {
+                detailDTO.setCategoryName(category.getName());
+            }
+        }
+        //查询状态
+        if (activity.getEnrollmentStatus() != null) {
+            detailDTO.setEnrollmentStatus(activity.getEnrollmentStatus());
+        } else {
+            detailDTO.setEnrollmentStatus(0); // 默认为待报名状态
+        }
+        // 查询区域信息
+        if (activity.getAreaId() != null) {
+            Area area = areaMapper.selectById(activity.getAreaId());
+            if (area != null) {
+                detailDTO.setAreaName(area.getName());
+            }
+        }
+
+        // 查询标签信息
+        List<Integer> tagIds = activityTagRelationService.getTagIdsByActivityId(activityId);
+        if (tagIds != null && !tagIds.isEmpty()) {
+            List<OrderTag> tags = orderTagService.listByIds(tagIds);
+            if (tags != null && !tags.isEmpty()) {
+                List<OrderActivityDetailDTO.OrderTagDTO> tagDTOs = tags.stream().map(tag -> {
+                    OrderActivityDetailDTO.OrderTagDTO tagDTO = new OrderActivityDetailDTO.OrderTagDTO();
+                    tagDTO.setId(tag.getId());
+                    tagDTO.setName(tag.getName());
+                    return tagDTO;
+                }).collect(Collectors.toList());
+                detailDTO.setTags(tagDTOs);
+            }
+        }
+
+        return detailDTO;
+    }
+
+    @Override
+    public PageResult<OrderActivityDTO> queryActivityList(OrderActivityQueryDTO queryDTO) {
+        // 创建分页对象
+        Page<OrderActivity> page = new Page<>(queryDTO.getPageNum(), queryDTO.getPageSize());
+
+        // 构建查询条件
+        LambdaQueryWrapper<OrderActivity> wrapper = new LambdaQueryWrapper<>();
+
+        // 添加区域筛选条件
+        if (queryDTO.getAreaId() != null) {
+            wrapper.eq(OrderActivity::getAreaId, queryDTO.getAreaId());
+        }
+
+        // 添加类别筛选条件
+        if (queryDTO.getCategoryId() != null) {
+            wrapper.eq(OrderActivity::getCategoryId, queryDTO.getCategoryId());
+        }
+
+        // 按创建时间倒序排序
+        wrapper.orderByDesc(OrderActivity::getCreateTime);
+
+        // 执行分页查询
+        Page<OrderActivity> activityPage = page(page, wrapper);
+
+        // 转换为DTO列表
+        List<OrderActivityDTO> activityDTOs = activityPage.getRecords().stream().map(activity -> {
+            OrderActivityDTO dto = new OrderActivityDTO();
+            BeanUtils.copyProperties(activity, dto);
+
+            // 查询发布者信息
+            User publisher = userMapper.selectById(activity.getPublisherId());
+            if (publisher != null) {
+                dto.setPublisherName(publisher.getRealName());
+                dto.setPublisherAvatar(publisher.getAvatar());
+                dto.setPublisherRating(publisher.getRating() != null ? publisher.getRating().intValue() : 0);
+            }
+
+            // 查询活动标签
+            List<Integer> tagIds = activityTagRelationService.getTagIdsByActivityId(activity.getId());
+            if (tagIds != null && !tagIds.isEmpty()) {
+                List<OrderTag> tags = orderTagService.listByIds(tagIds);
+                String tagNames = tags.stream().map(OrderTag::getName).collect(Collectors.joining(","));
+                dto.setTags(tagNames);
+            }
+
+            // 查询区域信息
+            if (activity.getAreaId() != null) {
+                Area area = areaMapper.selectById(activity.getAreaId());
+                if (area != null) {
+                    dto.setAreaName(area.getName());
+                }
+            }
+
+            // 查询类别信息
+            if (activity.getCategoryId() != null) {
+                ServiceCategory category = serviceCategoryMapper.selectById(activity.getCategoryId());
+                if (category != null) {
+                    dto.setCategoryName(category.getName());
+                }
+            }
+
+            // 使用数据库中的报名人数
+            dto.setCurrentNumber(activity.getCurrentNumber());
+
+            // 确保招募人数不为null
+            if (dto.getRecruitNumber() == null) {
+                dto.setRecruitNumber(20);
+            }
+
+            return dto;
+        }).collect(Collectors.toList());
+
+        // 构建分页结果
+        PageResult<OrderActivityDTO> result = new PageResult<>();
+        result.setList(activityDTOs);
+        result.setTotal(activityPage.getTotal());
+        result.setPages((int) activityPage.getPages());
+        result.setPageNum((int) activityPage.getCurrent());
+        result.setPageSize((int) activityPage.getSize());
+
+        return result;
+    }
+
+    private void updateActivityStatusToAccepted(Integer activityId) {
+        OrderActivity activity = getById(activityId);
+        if (activity == null || activity.getConnect() != 0) {
+            return;
+        }
+
+        // 更新状态为已接单
+        activity.setConnect(1);
+        if (updateById(activity)) {
+            // 清除缓存
+            String cacheKey = ACTIVITY_DETAIL_KEY_PREFIX + activityId;
+            stringRedisTemplate.delete(cacheKey);
+        }
+    }
+
+    public Map<String, Object> updateActivitySignupCount(Integer activityId) {
+        Map<String, Object> result = new HashMap<>();
+        result.put("activityId", activityId);
+
+        try {
+            OrderActivity activity = getById(activityId);
+            if (activity == null) {
+                result.put("error", "活动不存在");
+                return result;
+            }
+
+            result.put("activityTitle", activity.getTitle());
+            Integer oldCount = activity.getCurrentNumber();
+            result.put("oldCount", oldCount != null ? oldCount : 0);
+
+            // 查询实际报名人数
+            LambdaQueryWrapper<OrderSignup> signupQuery = new LambdaQueryWrapper<>();
+            signupQuery.eq(OrderSignup::getOrderId, activityId)
+                    .in(OrderSignup::getStatus, SIGNUP_STATUS_APPLIED, SIGNUP_STATUS_APPROVED);
+            int signupCount = Math.toIntExact(orderSignupMapper.selectCount(signupQuery));
+            result.put("newCount", signupCount);
+
+            // 只返回查询结果,不更新数据库
+            log.info("活动ID: {}, 标题: {}, 数据库记录人数: {}, 实际查询人数: {}",
+                    activityId, activity.getTitle(), oldCount, signupCount);
+
+            return result;
+        } catch (Exception e) {
+            result.put("error", e.getMessage());
+            return result;
+        }
+    }
+
+    /**
+     * 获取报名状态的名称
+     * @param status 状态值
+     * @return 状态名称
+     */
+    private String getStatusName(int status) {
+        switch (status) {
+            case SIGNUP_STATUS_APPLIED:
+                return "已报名(待审核)";
+            case SIGNUP_STATUS_APPROVED:
+                return "已通过";
+            case SIGNUP_STATUS_REJECTED:
+                return "已拒绝";
+            case SIGNUP_STATUS_CANCELED:
+                return "已取消";
+            default:
+                return "未知状态";
+        }
+    }
+
+    @Override
+    @Transactional
+    public OrderActivity createActivity(OrderActivityDTO activityDTO) {
+        // 创建活动实体
+        OrderActivity activity = new OrderActivity();
+        BeanUtils.copyProperties(activityDTO, activity);
+
+        // 设置初始状态
+        activity.setStatus(0); // 0-待接单
+        activity.setConnect(0); // 0-待接单
+        activity.setEnrollmentStatus(0); // 0-待报名
+        activity.setCurrentNumber(0); // 初始化报名人数为0
+        activity.setCreateTime(new Date());
+        activity.setUpdateTime(new Date());
+
+        // 保存活动
+        save(activity);
+
+        // 如果有标签,保存标签关系
+        if (activityDTO.getTagIds() != null && !activityDTO.getTagIds().isEmpty()) {
+            activityTagRelationService.saveBatchByActivityId(activity.getId(), activityDTO.getTagIds());
+        }
+
+        return activity;
+    }
+
+    /**
+     * 修复所有活动的报名人数
+     * 这个方法应该在管理员后台调用,不要在普通接口中使用
+     */
+    @Transactional
+    public void fixAllActivitySignupNumbers() {
+        log.info("开始修复所有活动的报名人数");
+
+        // 获取所有活动
+        List<OrderActivity> activities = list();
+        int fixedCount = 0;
+
+        for (OrderActivity activity : activities) {
+            // 查询实际报名人数
+            LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.eq(OrderSignup::getOrderId, activity.getId())
+                    .in(OrderSignup::getStatus, 0, 1); // 已报名或已通过状态
+            int actualCount = Math.toIntExact(orderSignupMapper.selectCount(queryWrapper));
+
+            // 如果数据库中的报名人数与实际不符,进行修复
+            if (activity.getCurrentNumber() == null || activity.getCurrentNumber() != actualCount) {
+                activity.setCurrentNumber(actualCount);
+                updateById(activity);
+                fixedCount++;
+                log.info("修复活动报名人数:活动ID={}, 标题={}, 原报名人数={}, 实际报名人数={}",
+                        activity.getId(), activity.getTitle(),
+                        activity.getCurrentNumber(), actualCount);
+            }
+        }
+
+        log.info("完成修复所有活动的报名人数,共修复 {} 个活动", fixedCount);
+    }
+
+    @Scheduled(cron = "0 */10 * * * *") // 每10分钟执行一次
+    public void syncSignupNumbers() {
+        log.info("开始同步活动报名人数...");
+        try {
+            // 获取所有进行中的活动
+            LambdaQueryWrapper<OrderActivity> queryWrapper = new LambdaQueryWrapper<>();
+            queryWrapper.in(OrderActivity::getStatus, 0, 1); // 待接单或招募中的活动
+            List<OrderActivity> activities = list(queryWrapper);
+
+            int updatedCount = 0;
+            for (OrderActivity activity : activities) {
+                // 查询实际报名人数
+                LambdaQueryWrapper<OrderSignup> signupQuery = new LambdaQueryWrapper<>();
+                signupQuery.eq(OrderSignup::getOrderId, activity.getId())
+                        .in(OrderSignup::getStatus, 0, 1); // 已报名或已通过状态
+                int actualCount = Math.toIntExact(orderSignupMapper.selectCount(signupQuery));
+
+                // 如果数据库中的人数与实际不符,更新数据库
+                if (activity.getCurrentNumber() == null || activity.getCurrentNumber() != actualCount) {
+                    activity.setCurrentNumber(actualCount);
+                    updateById(activity);
+                    updatedCount++;
+                    log.info("更新活动报名人数:活动ID={},旧人数={},新人数={}",
+                        activity.getId(), activity.getCurrentNumber(), actualCount);
+                }
+            }
+
+            log.info("活动报名人数同步完成,共更新{}个活动", updatedCount);
+        } catch (Exception e) {
+            log.error("同步活动报名人数时发生错误", e);
+        }
+    }
+
+    // 添加诊断方法
+    public Map<String, Object> diagnoseSignupNumbers(Integer activityId) {
+        Map<String, Object> result = new HashMap<>();
+
+        try {
+            // 获取活动信息
+            OrderActivity activity = getById(activityId);
+            if (activity == null) {
+                result.put("error", "活动不存在");
+                return result;
+            }
+
+            // 查询实际报名人数
+            LambdaQueryWrapper<OrderSignup> signupQuery = new LambdaQueryWrapper<>();
+            signupQuery.eq(OrderSignup::getOrderId, activityId)
+                    .in(OrderSignup::getStatus, 0, 1); // 已报名或已通过状态
+            int actualCount = Math.toIntExact(orderSignupMapper.selectCount(signupQuery));
+
+            // 获取报名详情
+            List<Map<String, Object>> signupDetails = orderSignupMapper.selectList(signupQuery)
+                .stream()
+                .map(signup -> {
+                    Map<String, Object> detail = new HashMap<>();
+                    detail.put("id", signup.getId());
+                    detail.put("userId", signup.getUserId());
+                    detail.put("status", signup.getStatus());
+                    return detail;
+                })
+                .collect(Collectors.toList());
+
+            result.put("activityId", activityId);
+            result.put("currentNumberInDB", activity.getCurrentNumber());
+            result.put("actualSignupCount", actualCount);
+            result.put("recruitNumber", activity.getRecruitNumber());
+            result.put("signupDetails", signupDetails);
+
+            // 检查是否需要修复
+            if (activity.getCurrentNumber() == null || activity.getCurrentNumber() != actualCount) {
+                result.put("needsFix", true);
+                result.put("suggestedFix", String.format(
+                    "需要将 current_number 从 %d 更新为 %d",
+                    activity.getCurrentNumber(),
+                    actualCount
+                ));
+            } else {
+                result.put("needsFix", false);
+            }
+
+        } catch (Exception e) {
+            result.put("error", "诊断过程中发生错误: " + e.getMessage());
+            log.error("诊断报名人数时发生错误", e);
+        }
+
+        return result;
+    }
+
+    // 添加修复方法
+    @Transactional
+    public boolean fixSignupNumbers(Integer activityId) {
+        try {
+            // 获取活动信息
+            OrderActivity activity = getById(activityId);
+            if (activity == null) {
+                log.error("修复报名人数失败:活动不存在,活动ID={}", activityId);
+                return false;
+            }
+
+            // 查询实际报名人数
+            LambdaQueryWrapper<OrderSignup> signupQuery = new LambdaQueryWrapper<>();
+            signupQuery.eq(OrderSignup::getOrderId, activityId)
+                    .in(OrderSignup::getStatus, 0, 1); // 已报名或已通过状态
+            int actualCount = Math.toIntExact(orderSignupMapper.selectCount(signupQuery));
+
+            // 更新数据库中的报名人数
+            activity.setCurrentNumber(actualCount);
+            boolean updated = updateById(activity);
+
+            if (updated) {
+                log.info("修复活动报名人数成功:活动ID={},更新为{}", activityId, actualCount);
+                // 清除相关缓存
+                String cacheKey = ACTIVITY_DETAIL_KEY_PREFIX + activityId;
+                stringRedisTemplate.delete(cacheKey);
+            }
+
+            return updated;
+        } catch (Exception e) {
+            log.error("修复报名人数时发生错误", e);
+            return false;
+        }
+    }
+}
+
+
+
+

+ 272 - 0
src/main/java/com/zhentao/baoming/service/impl/OrderSignupServiceImpl.java

@@ -0,0 +1,272 @@
+package com.zhentao.baoming.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.dto.order.OrderSignupDTO;
+import com.zhentao.baoming.mapper.OrderActivityMapper;
+import com.zhentao.baoming.mapper.OrderSignupMapper;
+import com.zhentao.baoming.pojo.OrderActivity;
+import com.zhentao.baoming.pojo.OrderSignup;
+import com.zhentao.baoming.service.OrderActivityService;
+import com.zhentao.baoming.service.OrderSignupService;
+import com.zhentao.user.domain.User;
+import com.zhentao.user.mapper.UserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_signup(点单报名表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+@Slf4j
+public class OrderSignupServiceImpl extends ServiceImpl<OrderSignupMapper, OrderSignup>
+    implements OrderSignupService {
+
+    @Autowired
+    private OrderActivityMapper orderActivityMapper;
+
+    @Autowired
+    private UserMapper userMapper;
+
+    @Autowired
+    private OrderActivityService orderActivityService;
+
+    @Override
+    public List<OrderSignupDTO> getUserSignupList(Long userId) {
+        // 查询用户的报名记录
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getUserId, userId)
+                .orderByDesc(OrderSignup::getId);  // 按ID倒序排序,最新报名在前
+        List<OrderSignup> signups = list(queryWrapper);
+
+        return convertToDTO(signups);
+    }
+
+    @Override
+    public List<OrderSignupDTO> getActivitySignupList(Integer activityId) {
+        // 查询活动的报名记录
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getOrderId, activityId)
+                .orderByDesc(OrderSignup::getId);  // 按ID倒序排序,最新报名在前
+        List<OrderSignup> signups = list(queryWrapper);
+
+        return convertToDTO(signups);
+    }
+
+    @Override
+    @Transactional
+    public boolean signupActivity(Long userId, Integer activityId, String comment) {
+        // 检查活动是否存在
+        OrderActivity activity = orderActivityMapper.selectById(activityId);
+        if (activity == null) {
+            log.error("报名失败:活动不存在,活动ID={}", activityId);
+            return false;
+        }
+
+        // 检查活动状态是否允许报名
+        if (activity.getStatus() != 0 && activity.getStatus() != 1) {
+            log.error("报名失败:活动状态不允许报名,活动ID={},状态={}", activityId, activity.getStatus());
+            return false;
+        }
+
+        // 检查是否已达到招募人数上限
+        int currentSignupCount = getActivitySignupCount(activityId);
+        if (activity.getRecruitNumber() != null && currentSignupCount >= activity.getRecruitNumber()) {
+            log.error("报名失败:活动已达到招募人数上限,活动ID={},当前报名人数={},招募人数={}",
+                    activityId, currentSignupCount, activity.getRecruitNumber());
+            return false;
+        }
+
+        // 检查用户是否已经报名
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getUserId, userId)
+                .eq(OrderSignup::getOrderId, activityId);
+        OrderSignup existSignup = getOne(queryWrapper);
+        if (existSignup != null) {
+            log.error("报名失败:用户已报名,用户ID={},活动ID={}", userId, activityId);
+            return false;
+        }
+
+        // 创建报名记录
+        OrderSignup signup = new OrderSignup();
+        signup.setUserId(userId);
+        signup.setOrderId(activityId);
+        signup.setComment(comment);
+        signup.setStatus(0); // 0-已报名,待审核
+        boolean saveResult = save(signup);
+
+        if (saveResult) {
+            // 更新活动的当前报名人数
+            updateActivitySignupCount(activityId);
+
+            // 检查是否需要更新活动状态为已接单
+            checkAndUpdateActivityStatus(activityId);
+
+            log.info("用户报名成功,用户ID={},活动ID={}", userId, activityId);
+        }
+
+        return saveResult;
+    }
+
+    @Override
+    @Transactional
+    public boolean cancelSignup(Long userId, Integer activityId) {
+        // 查询用户的报名记录
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getUserId, userId)
+                .eq(OrderSignup::getOrderId, activityId);
+        OrderSignup signup = getOne(queryWrapper);
+
+        if (signup == null) {
+            log.error("取消报名失败:未找到报名记录,用户ID={},活动ID={}", userId, activityId);
+            return false;
+        }
+
+        // 删除报名记录
+        boolean removeResult = removeById(signup.getId());
+
+        if (removeResult) {
+            // 更新活动的当前报名人数
+            updateActivitySignupCount(activityId);
+            log.info("用户取消报名成功,用户ID={},活动ID={}", userId, activityId);
+        }
+
+        return removeResult;
+    }
+
+    @Override
+    public int getActivitySignupCount(Integer activityId) {
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getOrderId, activityId)
+                .in(OrderSignup::getStatus, 0, 1); // 已报名或已通过状态
+        return Math.toIntExact(count(queryWrapper));
+    }
+
+    /**
+     * 更新活动的当前报名人数
+     */
+    private void updateActivitySignupCount(Integer activityId) {
+        // 查询当前报名人数
+        int signupCount = getActivitySignupCount(activityId);
+
+        // 更新活动的当前报名人数
+        OrderActivity activity = orderActivityMapper.selectById(activityId);
+        if (activity != null) {
+            activity.setCurrentNumber(signupCount);
+            orderActivityMapper.updateById(activity);
+            log.info("更新活动报名人数,活动ID={},当前报名人数={}", activityId, signupCount);
+        }
+    }
+
+    /**
+     * 检查并更新活动状态
+     */
+    private void checkAndUpdateActivityStatus(Integer activityId) {
+        OrderActivity activity = orderActivityMapper.selectById(activityId);
+        if (activity == null) {
+            return;
+        }
+
+        // 如果当前报名人数达到招募人数,且活动状态为待接单,则更新为已接单
+        int signupCount = getActivitySignupCount(activityId);
+        if (activity.getRecruitNumber() != null && signupCount >= activity.getRecruitNumber() && activity.getConnect() == 0) {
+            // 更新活动状态为已接单
+            activity.setConnect(1); // 1-已接单
+            orderActivityMapper.updateById(activity);
+            log.info("活动报名人数已满,自动更新状态为已接单,活动ID={}", activityId);
+        }
+    }
+
+    /**
+     * 将报名记录转换为DTO
+     */
+    private List<OrderSignupDTO> convertToDTO(List<OrderSignup> signups) {
+        List<OrderSignupDTO> dtoList = new ArrayList<>();
+
+        if (signups != null && !signups.isEmpty()) {
+            for (OrderSignup signup : signups) {
+                OrderSignupDTO dto = new OrderSignupDTO();
+                dto.setId(signup.getId());
+                dto.setOrderId(signup.getOrderId());
+                dto.setUserId(signup.getUserId());
+                dto.setStatus(signup.getStatus());
+                dto.setComment(signup.getComment());
+
+                // 查询活动信息
+                OrderActivity activity = orderActivityMapper.selectById(signup.getOrderId());
+                if (activity != null) {
+                    dto.setOrderTitle(activity.getTitle());
+                    dto.setOrderCoverImage(activity.getCoverImage());
+                    dto.setActivityTime(activity.getActivityTime());
+                }
+
+                // 查询用户信息
+                User user = userMapper.selectById(signup.getUserId());
+                if (user != null) {
+                    dto.setUsername(user.getUsername());
+                    dto.setRealName(user.getRealName());
+                    dto.setAvatar(user.getAvatar());
+                }
+
+                dtoList.add(dto);
+            }
+        }
+
+        return dtoList;
+    }
+
+    @Override
+    @Transactional
+    public boolean updateSignupStatus(Long userId, Integer activityId, Integer status) {
+        // 查询用户的报名记录
+        LambdaQueryWrapper<OrderSignup> queryWrapper = new LambdaQueryWrapper<>();
+        queryWrapper.eq(OrderSignup::getUserId, userId)
+                .eq(OrderSignup::getOrderId, activityId);
+        OrderSignup signup = getOne(queryWrapper);
+
+        if (signup == null) {
+            log.error("更新报名状态失败:未找到报名记录,用户ID={},活动ID={}", userId, activityId);
+            return false;
+        }
+
+        // 更新报名状态
+        signup.setStatus(status);
+        boolean updateResult = updateById(signup);
+
+        if (updateResult) {
+            // 更新活动的当前报名人数
+            updateActivitySignupCount(activityId);
+            log.info("更新报名状态成功,用户ID={},活动ID={},新状态={}", userId, activityId, status);
+
+            // 如果是审核通过,检查是否需要更新活动状态
+            if (status == 1) { // 1-审核通过
+                checkAndUpdateActivityStatus(activityId);
+            }
+        }
+
+        return updateResult;
+    }
+
+    @Override
+    @Transactional
+    public boolean approveSignup(Long userId, Integer activityId) {
+        return updateSignupStatus(userId, activityId, 1); // 1-审核通过
+    }
+
+    @Override
+    @Transactional
+    public boolean rejectSignup(Long userId, Integer activityId) {
+        return updateSignupStatus(userId, activityId, 2); // 2-已拒绝
+    }
+}
+
+
+
+

+ 42 - 0
src/main/java/com/zhentao/baoming/service/impl/OrderTagServiceImpl.java

@@ -0,0 +1,42 @@
+package com.zhentao.baoming.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.mapper.OrderTagMapper;
+import com.zhentao.baoming.pojo.OrderTag;
+import com.zhentao.baoming.service.ActivityTagRelationService;
+import com.zhentao.baoming.service.OrderTagService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【order_tag(点单标签表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+public class OrderTagServiceImpl extends ServiceImpl<OrderTagMapper, OrderTag>
+    implements OrderTagService {
+
+    @Autowired
+    private ActivityTagRelationService activityTagRelationService;
+
+    @Override
+    public List<OrderTag> getTagsByActivityId(Integer activityId) {
+        // 获取活动关联的标签ID列表
+        List<Integer> tagIds = activityTagRelationService.getTagIdsByActivityId(activityId);
+
+        if (tagIds == null || tagIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+
+        // 批量查询标签
+        return listByIds(tagIds);
+    }
+}
+
+
+
+

+ 22 - 0
src/main/java/com/zhentao/baoming/service/impl/ServiceCategoryServiceImpl.java

@@ -0,0 +1,22 @@
+package com.zhentao.baoming.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.zhentao.baoming.mapper.ServiceCategoryMapper;
+import com.zhentao.baoming.pojo.ServiceCategory;
+import com.zhentao.baoming.service.ServiceCategoryService;
+import org.springframework.stereotype.Service;
+
+/**
+* @author ZhuanZ(无密码)
+* @description 针对表【service_category(服务类别表)】的数据库操作Service实现
+* @createDate 2025-07-01 14:17:25
+*/
+@Service
+public class ServiceCategoryServiceImpl extends ServiceImpl<ServiceCategoryMapper, ServiceCategory>
+    implements ServiceCategoryService {
+
+}
+
+
+
+

+ 51 - 0
src/main/java/com/zhentao/common/oss/OSSService.java

@@ -0,0 +1,51 @@
+package com.zhentao.common.oss;
+
+import com.aliyun.oss.OSS;
+import com.aliyun.oss.OSSClientBuilder;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.UUID;
+
+@Service
+
+public class OSSService {
+
+    @Value("${aliyun.oss.endpoint}")
+    private String endpoint;
+
+    @Value("${aliyun.oss.accessKeyId}")
+    private String accessKeyId;
+
+    @Value("${aliyun.oss.accessKeySecret}")
+    private String accessKeySecret;
+
+    @Value("${aliyun.oss.bucketName}")
+    private String bucketName;
+
+    public String upload(MultipartFile file) throws IOException {
+        // 生成唯一文件名
+        String originalFilename = file.getOriginalFilename();
+        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf("."));
+        String objectName = UUID.randomUUID().toString() ;
+        Date date = new Date();
+        date.setTime(date.getTime() + 1000 * 60 * 60 * 24 * 7);
+        objectName = date.getTime() + objectName.substring(2,10)+ fileExtension;
+        System.out.println(objectName);
+        // 上传到OSS
+        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
+        try {
+            ossClient.putObject(bucketName, objectName, file.getInputStream());
+            return generateUrl(objectName);
+        } finally {
+            ossClient.shutdown();
+        }
+    }
+
+    private String generateUrl(String objectName) {
+        return "https://" + bucketName + "." + endpoint.replace("https://", "") + "/" + objectName;
+    }
+}

+ 52 - 0
src/main/java/com/zhentao/common/utils/Results.java

@@ -0,0 +1,52 @@
+package com.zhentao.common.utils;
+
+import lombok.Data;
+
+@Data
+public class Results<T> {
+    private Integer code;
+    private T data;
+    private String msg;
+
+    public static <T> Results<T> success(T data) {
+        Results<T> result = new Results<>();
+        result.setCode(200);
+        result.setData(data);
+        result.setMsg("操作成功");
+        return result;
+    }
+
+    public static <T> Results<T> success(T data, String msg) {
+        Results<T> result = new Results<>();
+        result.setCode(200);
+        result.setData(data);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static <T> Results<T> error(String msg) {
+        Results<T> result = new Results<>();
+        result.setCode(400);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static <T> Results<T> error(Integer code, String msg) {
+        Results<T> result = new Results<>();
+        result.setCode(code);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public static <T> Results<T> error(Integer code, T data, String msg) {
+        Results<T> result = new Results<>();
+        result.setCode(code);
+        result.setData(data);
+        result.setMsg(msg);
+        return result;
+    }
+
+    public void put(String url, String imageUrl) {
+
+    }
+}

+ 3 - 1
src/main/java/com/zhentao/common/utils/TokenUtils.java

@@ -95,5 +95,7 @@ public class TokenUtils {
         Date expiration = getAllClaimsFromToken(token).getExpiration();
         return expiration.before(new Date());
     }
-
+    public static String getTokenFromRequest() {
+        return null;
+    }
 }

+ 49 - 0
src/main/java/com/zhentao/common/vo/ResultVo.java

@@ -0,0 +1,49 @@
+package com.zhentao.common.vo;
+
+
+
+public class ResultVo<T> {
+    private int code;
+    private String msg;
+    private T data;
+
+    public static <T> ResultVo<T> success(T data) {
+        ResultVo<T> result = new ResultVo<>();
+        result.setCode(200);
+        result.setMsg("成功");
+        result.setData(data);
+        return result;
+    }
+
+    public static <T> ResultVo<T> error(int code, String msg) {
+        ResultVo<T> result = new ResultVo<>();
+        result.setCode(code);
+        result.setMsg(msg);
+        return result;
+    }
+
+    // getters and setters
+    public int getCode() {
+        return code;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public String getMsg() {
+        return msg;
+    }
+
+    public void setMsg(String msg) {
+        this.msg = msg;
+    }
+
+    public T getData() {
+        return data;
+    }
+
+    public void setData(T data) {
+        this.data = data;
+    }
+}

+ 1 - 0
src/main/java/com/zhentao/community/dto/PostVO.java

@@ -7,6 +7,7 @@ import java.util.Date;
 @Data
 public class PostVO {
     private String postId;
+    private String userId;
     private String title;
     private String content;
     private String mediaUrls;      // 图片/视频URL,字符串或JSON数组

+ 1 - 0
src/main/java/com/zhentao/community/service/impl/PostsServiceImpl.java

@@ -258,6 +258,7 @@ public class PostsServiceImpl extends ServiceImpl<PostsMapper, Posts>
                               Map<Long, Integer> shareCountMap,
                               Set<Long> likedPostIds) {
         PostVO vo = new PostVO();
+        vo.setUserId(String.valueOf(post.getUserId()));
         vo.setPostId(String.valueOf(post.getPostId()));
         vo.setTitle(post.getTitle());
         vo.setContent(post.getContent());

+ 33 - 0
src/main/java/com/zhentao/user/controller/UserController.java

@@ -1,17 +1,22 @@
 package com.zhentao.user.controller;
 
 import com.zhentao.common.config.NullLogin;
+import com.zhentao.common.utils.Results;
 import com.zhentao.common.utils.TokenUtils;
 import com.zhentao.common.vo.Result;
 import com.zhentao.user.dto.NoteDto;
 import com.zhentao.user.dto.UserDto;
+import com.zhentao.user.dto.UserStatusDto;
 import com.zhentao.user.dto.User_dto;
 import com.zhentao.user.service.UserOnlineStatusService;
 import com.zhentao.user.service.UserService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
 
 @RestController
 @RequestMapping("user")
@@ -69,4 +74,32 @@ public class UserController {
         Long userId = Long.parseLong(userIdFromToken);
         return userService.updateUserLocation(userId, jingdu, weidu);
     }
+    /**
+     * 人脸登录
+     * @param file
+     * @return
+     */
+    @PostMapping("files")
+    public Results files(@RequestParam("file") MultipartFile file) {
+        return userService.faceLogin(file);
+    }
+
+    //上传照片
+    @PostMapping("upload")
+    public Results upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException {
+        return userService.upload(file, request);
+    }
+
+    /**
+     * zhuantai
+     * @param userDto
+     * @param request
+     * @return
+     */
+    //状态改变
+    @PostMapping("/enableStatus")
+    public Results enableStatus(@RequestBody UserStatusDto userDto, HttpServletRequest  request) {
+        return userService.enableStatus(userDto,request);
+    }
+
 }

+ 4 - 1
src/main/java/com/zhentao/user/domain/User.java

@@ -105,7 +105,10 @@ public class User implements Serializable {
      * 是否启用人脸登录(0-禁用,1-启用)
      */
     private Integer faceLoginEnabled;
-
+    /**
+     * 用户评分
+     */
+    private Double rating;
     /**
      * 创建时间
      */

+ 14 - 0
src/main/java/com/zhentao/user/dto/UserStatusDto.java

@@ -0,0 +1,14 @@
+package com.zhentao.user.dto;
+
+import lombok.Data;
+
+import javax.validation.constraints.NotBlank;
+
+@Data
+public class UserStatusDto {
+    /**
+     * 是否启用人脸登录(0-禁用,1-启用)
+     */
+    @NotBlank(message = "启动状态不能为空")
+    private Integer faceLoginEnabled;
+}

+ 297 - 0
src/main/java/com/zhentao/user/face_recognition/FaceEngineUtil.java

@@ -0,0 +1,297 @@
+package com.zhentao.user.face_recognition;
+
+import com.arcsoft.face.*;
+import com.arcsoft.face.enums.DetectMode;
+import com.arcsoft.face.enums.DetectOrient;
+import com.arcsoft.face.enums.ErrorInfo;
+import com.arcsoft.face.toolkit.ImageFactory;
+import com.arcsoft.face.toolkit.ImageInfo;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.imageio.ImageIO;
+import java.awt.image.BufferedImage;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+public class FaceEngineUtil {
+
+    private static final String APP_ID = "DAWb2s812WA9brCxzNANQBv4TGZRnQVxBrBSYQ3zUWqe";
+    private static final String SDK_KEY = "CHSAsvtkLBDbwN6ereNwr9zwD4e1b4a56SzZgqVZFHHe";
+    private static final String ENGINE_PATH = new File("libs/WIN64").getAbsolutePath();
+
+    public static float faceAnalysis(String imageUrl1, MultipartFile imageUrl2) {
+        FaceEngine faceEngine = new FaceEngine(ENGINE_PATH);
+        float similarity = 0.0f;
+        // 激活引擎
+        int errorCode = faceEngine.activeOnline(APP_ID, SDK_KEY);
+
+        if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
+            System.out.println("引擎激活失败");
+            return similarity;
+        }
+
+        ActiveFileInfo activeFileInfo = new ActiveFileInfo();
+        errorCode = faceEngine.getActiveFileInfo(activeFileInfo);
+        if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
+            System.out.println("获取激活文件信息失败");
+            return similarity;
+        }
+
+        // 引擎配置
+        EngineConfiguration engineConfiguration = new EngineConfiguration();
+        engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
+        engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
+        engineConfiguration.setDetectFaceMaxNum(10);
+        engineConfiguration.setDetectFaceScaleVal(16);
+        // 功能配置
+        FunctionConfiguration functionConfiguration = new FunctionConfiguration();
+        functionConfiguration.setSupportAge(true);
+        functionConfiguration.setSupportFace3dAngle(true);
+        functionConfiguration.setSupportFaceDetect(true);
+        functionConfiguration.setSupportFaceRecognition(true);
+        functionConfiguration.setSupportGender(true);
+        functionConfiguration.setSupportLiveness(true);
+        functionConfiguration.setSupportIRLiveness(true);
+        engineConfiguration.setFunctionConfiguration(functionConfiguration);
+
+        // 初始化引擎
+        errorCode = faceEngine.init(engineConfiguration);
+
+        if (errorCode != ErrorInfo.MOK.getValue()) {
+            System.out.println("初始化引擎失败");
+            return similarity;
+        }
+
+        try {
+            // 人脸检测
+            ImageInfo imageInfo = getImageInfoFromUrl(imageUrl1);
+            List<FaceInfo> faceInfoList = new ArrayList<>();
+            errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
+            if (faceInfoList.isEmpty()) {
+                System.out.println("未检测到人脸1");
+                return similarity;
+            }
+
+            // 特征提取
+            FaceFeature faceFeature = new FaceFeature();
+            errorCode = faceEngine.extractFaceFeature(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList.get(0), faceFeature);
+
+            // 人脸检测2
+            ImageInfo imageInfo2 = getImageInfoFromMultipartFile(imageUrl2);
+            List<FaceInfo> faceInfoList2 = new ArrayList<>();
+            errorCode = faceEngine.detectFaces(imageInfo2.getImageData(), imageInfo2.getWidth(), imageInfo2.getHeight(), imageInfo2.getImageFormat(), faceInfoList2);
+            if (faceInfoList2.isEmpty()) {
+                System.out.println("未检测到人脸2");
+                return similarity;
+            }
+
+            // 特征提取2
+            FaceFeature faceFeature2 = new FaceFeature();
+            errorCode = faceEngine.extractFaceFeature(imageInfo2.getImageData(), imageInfo2.getWidth(), imageInfo2.getHeight(), imageInfo2.getImageFormat(), faceInfoList2.get(0), faceFeature2);
+
+            // 特征比对
+            FaceFeature targetFaceFeature = new FaceFeature();
+            targetFaceFeature.setFeatureData(faceFeature.getFeatureData());
+            FaceFeature sourceFaceFeature = new FaceFeature();
+            sourceFaceFeature.setFeatureData(faceFeature2.getFeatureData());
+            FaceSimilar faceSimilar = new FaceSimilar();
+
+            errorCode = faceEngine.compareFaceFeature(targetFaceFeature, sourceFaceFeature, faceSimilar);
+            similarity = faceSimilar.getScore();
+            System.out.println("相似度:" + similarity);
+
+            return similarity;
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return similarity;
+        } finally {
+            // 释放引擎
+            if (faceEngine != null) {
+                faceEngine.unInit();
+            }
+        }
+    }
+
+    private static ImageInfo getImageInfoFromUrl(String imageUrl) {
+        try {
+            URL url = new URL(imageUrl);
+            BufferedImage bufferedImage = ImageIO.read(url);
+
+            // 将BufferedImage转换为byte数组
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(bufferedImage, "jpg", baos);
+            byte[] imageData = baos.toByteArray();
+
+            // 使用byte数组创建ImageInfo
+            return ImageFactory.getRGBData(imageData);
+        } catch (Exception e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static ImageInfo getImageInfoFromMultipartFile(MultipartFile file) {
+        try {
+            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(bufferedImage, "jpg", baos);
+            byte[] imageData = baos.toByteArray();
+            return ImageFactory.getRGBData(imageData);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    private static ImageInfo getGrayImageInfoFromMultipartFile(MultipartFile file) {
+        try {
+            BufferedImage bufferedImage = ImageIO.read(file.getInputStream());
+            ByteArrayOutputStream baos = new ByteArrayOutputStream();
+            ImageIO.write(bufferedImage, "jpg", baos);
+            byte[] imageData = baos.toByteArray();
+            return ImageFactory.getGrayData(imageData);
+        } catch (IOException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    /**
+     * 活体检测结果类
+     */
+    public static class LivenessResult {
+        private boolean isLive;
+        private float rgbScore;
+        private float irScore;
+        private String message;
+        private boolean success;
+
+        public LivenessResult(boolean isLive, float rgbScore, float irScore, String message, boolean success) {
+            this.isLive = isLive;
+            this.rgbScore = rgbScore;
+            this.irScore = irScore;
+            this.message = message;
+            this.success = success;
+        }
+
+        public boolean isLive() { return isLive; }
+        public float getRgbScore() { return rgbScore; }
+        public float getIrScore() { return irScore; }
+        public String getMessage() { return message; }
+        public boolean isSuccess() { return success; }
+    }
+
+    /**
+     * 进行活体检测
+     * @param rgbImage RGB图像
+     * @param irImage IR图像(可选)
+     * @return 活体检测结果
+     */
+    public static LivenessResult livenessDetection(MultipartFile rgbImage, MultipartFile irImage) {
+        FaceEngine faceEngine = new FaceEngine(ENGINE_PATH);
+        try {
+            // 激活引擎
+            int errorCode = faceEngine.activeOnline(APP_ID, SDK_KEY);
+            if (errorCode != ErrorInfo.MOK.getValue() && errorCode != ErrorInfo.MERR_ASF_ALREADY_ACTIVATED.getValue()) {
+                return new LivenessResult(false, 0, 0, "引擎激活失败", false);
+            }
+
+            // 引擎配置
+            EngineConfiguration engineConfiguration = new EngineConfiguration();
+            engineConfiguration.setDetectMode(DetectMode.ASF_DETECT_MODE_IMAGE);
+            engineConfiguration.setDetectFaceOrientPriority(DetectOrient.ASF_OP_ALL_OUT);
+            engineConfiguration.setDetectFaceMaxNum(1);
+            engineConfiguration.setDetectFaceScaleVal(16);
+
+            // 功能配置
+            FunctionConfiguration functionConfiguration = new FunctionConfiguration();
+            functionConfiguration.setSupportFaceDetect(true);
+            functionConfiguration.setSupportLiveness(true);
+            functionConfiguration.setSupportIRLiveness(true);
+            engineConfiguration.setFunctionConfiguration(functionConfiguration);
+
+            // 初始化引擎
+            errorCode = faceEngine.init(engineConfiguration);
+            if (errorCode != ErrorInfo.MOK.getValue()) {
+                return new LivenessResult(false, 0, 0, "引擎初始化失败", false);
+            }
+
+            // 设置活体检测参数
+            errorCode = faceEngine.setLivenessParam(0.5f, 0.7f);
+
+            // RGB活体检测
+            float rgbScore = 0.0f;
+            boolean isRgbLive = false;
+            if (rgbImage != null) {
+                ImageInfo imageInfo = getImageInfoFromMultipartFile(rgbImage);
+                if (imageInfo != null) {
+                    List<FaceInfo> faceInfoList = new ArrayList<>();
+                    errorCode = faceEngine.detectFaces(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList);
+
+                    if (!faceInfoList.isEmpty()) {
+                        errorCode = faceEngine.process(imageInfo.getImageData(), imageInfo.getWidth(), imageInfo.getHeight(), imageInfo.getImageFormat(), faceInfoList, functionConfiguration);
+
+                        List<LivenessInfo> livenessInfoList = new ArrayList<>();
+                        errorCode = faceEngine.getLiveness(livenessInfoList);
+                        if (!livenessInfoList.isEmpty()) {
+                            rgbScore = livenessInfoList.get(0).getLiveness();
+                            isRgbLive = rgbScore >= 0.7f;
+                        }
+                    }
+                }
+            }
+
+            // IR活体检测
+            float irScore = 0.0f;
+            boolean isIrLive = false;
+            if (irImage != null) {
+                ImageInfo irImageInfo = getGrayImageInfoFromMultipartFile(irImage);
+                if (irImageInfo != null) {
+                    List<FaceInfo> irFaceInfoList = new ArrayList<>();
+                    errorCode = faceEngine.detectFaces(irImageInfo.getImageData(), irImageInfo.getWidth(), irImageInfo.getHeight(), irImageInfo.getImageFormat(), irFaceInfoList);
+
+                    if (!irFaceInfoList.isEmpty()) {
+                        FunctionConfiguration irConfig = new FunctionConfiguration();
+                        irConfig.setSupportIRLiveness(true);
+                        errorCode = faceEngine.processIr(irImageInfo.getImageData(), irImageInfo.getWidth(), irImageInfo.getHeight(), irImageInfo.getImageFormat(), irFaceInfoList, irConfig);
+
+                        List<IrLivenessInfo> irLivenessInfoList = new ArrayList<>();
+                        errorCode = faceEngine.getLivenessIr(irLivenessInfoList);
+                        if (!irLivenessInfoList.isEmpty()) {
+                            irScore = irLivenessInfoList.get(0).getLiveness();
+                            isIrLive = irScore >= 0.7f;
+                        }
+                    }
+                }
+            }
+
+            // 综合判断结果
+            boolean finalResult = false;
+            String message = "";
+            if (irImage == null) {
+                // 仅RGB检测
+                finalResult = isRgbLive;
+                message = isRgbLive ? "RGB活体检测通过" : "RGB活体检测不通过";
+            } else {
+                // RGB+IR双重检测
+                finalResult = isRgbLive && isIrLive;
+                message = finalResult ? "RGB+IR活体检测通过" : "RGB+IR活体检测不通过";
+            }
+
+            return new LivenessResult(finalResult, rgbScore, irScore, message, true);
+
+        } catch (Exception e) {
+            e.printStackTrace();
+            return new LivenessResult(false, 0, 0, "活体检测过程出现异常: " + e.getMessage(), false);
+        } finally {
+            if (faceEngine != null) {
+                faceEngine.unInit();
+            }
+        }
+    }
+}

+ 11 - 0
src/main/java/com/zhentao/user/service/UserService.java

@@ -1,10 +1,16 @@
 package com.zhentao.user.service;
 
+import com.zhentao.common.utils.Results;
 import com.zhentao.common.vo.Result;
 import com.zhentao.user.domain.User;
 import com.baomidou.mybatisplus.extension.service.IService;
 import com.zhentao.user.dto.NoteDto;
 import com.zhentao.user.dto.UserDto;
+import com.zhentao.user.dto.UserStatusDto;
+import org.springframework.web.multipart.MultipartFile;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
 
 /**
 * @author 86183
@@ -23,4 +29,9 @@ public interface UserService extends IService<User> {
 
     // 添加更新经纬度的方法
     Result updateUserLocation(Long userId, String jingdu, String weidu);
+    Results faceLogin(MultipartFile file);
+
+    Results upload(MultipartFile file, HttpServletRequest request) throws IOException;
+
+    Results enableStatus(UserStatusDto userDto, HttpServletRequest  request);
 }

+ 92 - 0
src/main/java/com/zhentao/user/service/impl/UserServiceImpl.java

@@ -1,11 +1,14 @@
 package com.zhentao.user.service.impl;
 
 import cn.hutool.core.util.IdUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zhentao.common.enums.ApiServerException;
 import com.zhentao.common.exception.AsynException;
+import com.zhentao.common.oss.OSSService;
 import com.zhentao.common.utils.HttpUtils;
+import com.zhentao.common.utils.Results;
 import com.zhentao.common.utils.TokenUtils;
 import com.zhentao.common.vo.Result;
 import com.zhentao.community.domain.Posts;
@@ -16,6 +19,8 @@ import com.zhentao.user.domain.UserMoney;
 import com.zhentao.user.domain.UserMutualFollows;
 import com.zhentao.user.dto.NoteDto;
 import com.zhentao.user.dto.UserDto;
+import com.zhentao.user.dto.UserStatusDto;
+import com.zhentao.user.face_recognition.FaceEngineUtil;
 import com.zhentao.user.service.UserMoneyService;
 import com.zhentao.user.service.UserMutualFollowsService;
 import com.zhentao.user.service.UserOnlineStatusService;
@@ -29,7 +34,10 @@ import org.springframework.context.annotation.Lazy;
 import org.springframework.data.redis.core.StringRedisTemplate;
 import org.springframework.stereotype.Service;
 import org.springframework.util.DigestUtils;
+import org.springframework.web.multipart.MultipartFile;
 
+import javax.servlet.http.HttpServletRequest;
+import java.io.IOException;
 import java.util.*;
 import java.util.concurrent.TimeUnit;
 
@@ -332,7 +340,91 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
         List<User> list = this.list();
         return Result.OK(list,"查询成功");
     }
+    /**
+     *人脸登录
+     * @param file
+     * @return
+     */
+    @Override
+    public Results faceLogin(MultipartFile file) {
+
+        List<User> list = this.list(new LambdaQueryWrapper<User>()
+                .eq(User::getFaceLoginEnabled, 1)
+                .isNotNull(User::getFaceImageUrl)
+                .ne(User::getFaceImageUrl, "")
+        );
+        User userFile = null;
+        float maxSimilarity = 0f;
+        for (User user : list) {
+            String faceImageUrl = user.getFaceImageUrl();
+            float v = FaceEngineUtil.faceAnalysis(faceImageUrl, file);
+            if (maxSimilarity<v){
+                maxSimilarity=v;
+                userFile = user;
+            }
+        }
+
+        if (maxSimilarity<0.8){
+            return Results.error(401,"人脸识别错误");
+        }
+        String token = TokenUtils.generateToken(String.valueOf(userFile.getId()));
+        Map<String,Object> map = new HashMap<>();
+        map.put("userId",userFile.getId()+"");
+        map.put("token",token);
+        //用户上线
+        onlineStatusService.userGoOnline(userFile.getId());
+        return Results.success(map,"登录成功");
+
+    }
+    @Autowired
+    private OSSService ossService;
+    @Override
+    public Results upload(MultipartFile file, HttpServletRequest request) throws IOException {
+        String token = request.getHeader("token");
+        String userId = TokenUtils.getUserIdFromToken(token);
+        if (userId==null){
+            return Results.error(401,"请先登录");
+        }
+
+        User byId = this.getById(userId);
+        if (byId==null){
+            return Results.error(401,"用户不存在");
+        }
+
+        String imgUpload = ossService.upload(file);
+        byId.setFaceImageUrl(imgUpload);
+        byId.setBirthDate(new Date());
+
+        boolean updated = this.updateById(byId);
+        System.err.println("更新结果: " + (updated ? "成功" : "失败"));
+
+        return Results.success("上传成功");
+    }
 
+    @Override
+    public Results enableStatus(UserStatusDto userDto, HttpServletRequest  request) {
+        String token = request.getHeader("token");
+        String userId = TokenUtils.getUserIdFromToken(token);
+        if (userId==null){
+            return Results.error(401,"请先登录");
+        }
+        User user = this.getById(userId);
+        if (user==null){
+            return Results.error(401,"用户不存在");
+        }
+        //登录识别状态进行修改
+        if (user.getFaceLoginEnabled()==1){
+            user.setFaceLoginEnabled(0);
+            user.setLastLoginTime(new Date());
+            this.updateById( user);
+            return Results.success("人脸识别已关闭");
+        }
+        //更新人脸识别
+        user.setFaceLoginEnabled(userDto.getFaceLoginEnabled());
+        user.setLastLoginTime(new Date());
+        this.updateById( user);
+        return Results.success("人脸识别已开启");
+    }
 }
 
 

+ 16 - 0
src/main/resources/com/zhentao/mapper/ActivityTagRelationMapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.ActivityTagRelationMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.ActivityTagRelation">
+            <id property="id" column="id" />
+            <result property="activityId" column="activity_id" />
+            <result property="tagId" column="tag_id" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,activity_id,tag_id
+    </sql>
+</mapper>

+ 17 - 0
src/main/resources/com/zhentao/mapper/AreaMapper.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.AreaMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.Area">
+            <id property="id" column="id" />
+            <result property="name" column="name" />
+            <result property="parentId" column="parent_id" />
+            <result property="level" column="level" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,name,parent_id,level
+    </sql>
+</mapper>

+ 21 - 0
src/main/resources/com/zhentao/mapper/EvaluationMapper.xml

@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.EvaluationMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.Evaluation">
+            <id property="id" column="id" />
+            <result property="orderId" column="order_id" />
+            <result property="userId" column="user_id" />
+            <result property="targetId" column="target_id" />
+            <result property="rating" column="rating" />
+            <result property="content" column="content" />
+            <result property="createTime" column="create_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,order_id,user_id,target_id,rating,content,
+        create_time
+    </sql>
+</mapper>

+ 33 - 0
src/main/resources/com/zhentao/mapper/OrderActivityMapper.xml

@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.OrderActivityMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.OrderActivity">
+            <id property="id" column="id" />
+            <result property="title" column="title" />
+            <result property="publisherId" column="publisher_id" />
+            <result property="categoryId" column="category_id" />
+            <result property="content" column="content" />
+            <result property="areaId" column="area_id" />
+            <result property="address" column="address" />
+            <result property="activityTime" column="activity_time" />
+            <result property="recruitStart" column="recruit_start" />
+            <result property="recruitEnd" column="recruit_end" />
+            <result property="recruitNumber" column="recruit_number" />
+            <result property="currentNumber" column="current_number" />
+            <result property="status" column="status" />
+            <result property="connect" column="connect" />
+            <result property="enrollmentStatus" column="enrollment_status" />
+            <result property="coverImage" column="cover_image" />
+            <result property="createTime" column="create_time" />
+            <result property="updateTime" column="update_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,title,publisher_id,category_id,content,area_id,
+        address,activity_time,recruit_start,recruit_end,recruit_number,
+        current_number,enrollment_status,status,cover_image,create_time,update_time
+    </sql>
+</mapper>

+ 18 - 0
src/main/resources/com/zhentao/mapper/OrderSignupMapper.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.OrderSignupMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.OrderSignup">
+            <id property="id" column="id" />
+            <result property="orderId" column="order_id" />
+            <result property="userId" column="user_id" />
+            <result property="status" column="status" />
+            <result property="comment" column="comment" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,order_id,user_id,status,comment
+    </sql>
+</mapper>

+ 16 - 0
src/main/resources/com/zhentao/mapper/OrderTagMapper.xml

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.OrderTagMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.OrderTag">
+            <id property="id" column="id" />
+            <result property="name" column="name" />
+            <result property="createTime" column="create_time" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,name,create_time
+    </sql>
+</mapper>

+ 17 - 0
src/main/resources/com/zhentao/mapper/ServiceCategoryMapper.xml

@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper
+        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
+        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.zhentao.order.mapper.ServiceCategoryMapper">
+
+    <resultMap id="BaseResultMap" type="com.zhentao.baoming.pojo.ServiceCategory">
+            <id property="id" column="id" />
+            <result property="name" column="name" />
+            <result property="icon" column="icon" />
+            <result property="sortOrder" column="sort_order" />
+    </resultMap>
+
+    <sql id="Base_Column_List">
+        id,name,icon,sort_order
+    </sql>
+</mapper>