From c22b60f532f8a3c5c6d2e76691fd79bdc2c584d8 Mon Sep 17 00:00:00 2001 From: wangfukang <15630117759@163.com> Date: Fri, 31 Jul 2026 17:13:46 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5=E6=8B=BC=E5=9B=A2=E6=95=B0?= =?UTF-8?q?=E6=8D=AE1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application.yml | 2 +- hiver-admin/test-output/test-report.html | 16 +- .../main/java/cc/hiver/core/dao/UserDao.java | 4 + .../java/cc/hiver/core/dao/WorkerDao.java | 4 + .../java/cc/hiver/core/entity/Worker.java | 4 + .../cc/hiver/core/service/UserService.java | 2 + .../cc/hiver/core/service/WorkerService.java | 2 + .../core/serviceimpl/UserServiceImpl.java | 5 + .../core/serviceimpl/WorkerServiceImpl.java | 5 + .../controller/manage/AuthController.java | 83 +- .../controller/manage/UserController.java | 47 + .../controller/CampusAdminController.java | 164 ++ .../campus/controller/CampusController.java | 224 +++ .../mall/campus/dto/CampusAdminActionDTO.java | 24 + .../mall/campus/dto/CampusChatMessageDTO.java | 12 + .../mall/campus/dto/CampusCommentDTO.java | 14 + .../mall/campus/dto/CampusComplaintDTO.java | 15 + .../mall/campus/dto/CampusPageQuery.java | 25 + .../hiver/mall/campus/dto/CampusPostDTO.java | 28 + .../mall/campus/entity/CampusAdminLog.java | 31 + .../mall/campus/entity/CampusCategory.java | 35 + .../mall/campus/entity/CampusChatMessage.java | 38 + .../mall/campus/entity/CampusChatSession.java | 50 + .../mall/campus/entity/CampusComment.java | 53 + .../mall/campus/entity/CampusComplaint.java | 59 + .../hiver/mall/campus/entity/CampusLike.java | 23 + .../hiver/mall/campus/entity/CampusPost.java | 107 ++ .../mall/campus/entity/CampusUserLimit.java | 33 + .../campus/mapper/CampusAdminLogMapper.java | 9 + .../campus/mapper/CampusCategoryMapper.java | 9 + .../mapper/CampusChatMessageMapper.java | 9 + .../mapper/CampusChatSessionMapper.java | 9 + .../campus/mapper/CampusCommentMapper.java | 9 + .../campus/mapper/CampusComplaintMapper.java | 9 + .../mall/campus/mapper/CampusLikeMapper.java | 9 + .../mall/campus/mapper/CampusPostMapper.java | 9 + .../campus/mapper/CampusUserLimitMapper.java | 9 + .../mall/campus/service/CampusService.java | 93 ++ .../service/impl/CampusServiceImpl.java | 1438 +++++++++++++++++ .../mall/campus/vo/CampusPostDetailVO.java | 12 + .../AdminDataStatisticsController.java | 103 +- .../hiver/mall/controller/ShopController.java | 121 +- .../controller/UserAddressController.java | 21 +- .../mall/controller/WorkerController.java | 52 +- .../controller/WorkerRelaPriceController.java | 59 +- .../dao/mapper/WorkerRelaPriceMapper.java | 4 + .../hiver/mall/entity/MallDeliveryOrder.java | 4 + .../cc/hiver/mall/entity/UserAddress.java | 3 + .../cc/hiver/mall/entity/WorkerRelaPrice.java | 3 + .../mall/fishmarket/dto/FishCommentDTO.java | 2 + .../mall/fishmarket/dto/FishPublishDTO.java | 2 + .../service/impl/FishMarketServiceImpl.java | 51 +- .../mall/ie/controller/IeController.java | 7 + .../impl/IeAliyunModerationServiceImpl.java | 64 +- .../impl/IeSecurityAuditServiceImpl.java | 5 +- .../cc/hiver/mall/ie/vo/IeAuditResult.java | 1 + .../service/impl/PlanetAdminServiceImpl.java | 12 +- .../service/impl/PlanetDrawServiceImpl.java | 14 +- .../cc/hiver/mall/pojo/vo/WorkerMatchVO.java | 3 + .../hiver/mall/pojo/vo/WorkerRealPriceVo.java | 3 + .../service/mybatis/UserAddressService.java | 6 +- .../mybatis/WorkerRelaPriceService.java | 4 + .../mall/serviceimpl/ShopServiceImpl.java | 8 + .../mybatis/MallDeliveryOrderServiceImpl.java | 41 +- .../mybatis/UserAddressServiceImpl.java | 8 +- .../mybatis/WorkerRelaPriceServiceImpl.java | 10 + .../src/main/resources/db/campus_circle.sql | 253 +++ .../resources/db/user_address_region_id.sql | 3 + .../main/resources/db/worker_full_time.sql | 3 + .../db/worker_rela_price_region_id.sql | 12 + .../mapper/MallDeliveryOrderMapper.xml | 51 +- .../mapper/WorkerRelaPriceMapper.xml | 32 +- 72 files changed, 3535 insertions(+), 163 deletions(-) create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusAdminController.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusController.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusAdminActionDTO.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusChatMessageDTO.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusCommentDTO.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusComplaintDTO.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPageQuery.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPostDTO.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusAdminLog.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusCategory.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatMessage.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatSession.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComment.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComplaint.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusLike.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusPost.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusUserLimit.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusAdminLogMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCategoryMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatMessageMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatSessionMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCommentMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusComplaintMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusLikeMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusPostMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusUserLimitMapper.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/CampusService.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/impl/CampusServiceImpl.java create mode 100644 hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/vo/CampusPostDetailVO.java create mode 100644 hiver-modules/hiver-mall/src/main/resources/db/campus_circle.sql create mode 100644 hiver-modules/hiver-mall/src/main/resources/db/user_address_region_id.sql create mode 100644 hiver-modules/hiver-mall/src/main/resources/db/worker_full_time.sql create mode 100644 hiver-modules/hiver-mall/src/main/resources/db/worker_rela_price_region_id.sql diff --git a/hiver-admin/src/main/resources/application.yml b/hiver-admin/src/main/resources/application.yml index 54148e3c..c2a7cce4 100644 --- a/hiver-admin/src/main/resources/application.yml +++ b/hiver-admin/src/main/resources/application.yml @@ -13,7 +13,7 @@ ie: region: cn-beijing endpoint: green-cip-vpc.cn-beijing.aliyuncs.com # 控制台规则配置的 serviceCode1 - text-service: chat_detection + text-service: comment_detection_pro image-service: baselineCheck video-service: videoDetection # 注意:语音审核 service 是下划线格式(视频是驼峰,阿里云两个产品命名不一致) diff --git a/hiver-admin/test-output/test-report.html b/hiver-admin/test-output/test-report.html index 536b418c..bbe84319 100644 --- a/hiver-admin/test-output/test-report.html +++ b/hiver-admin/test-output/test-report.html @@ -35,7 +35,7 @@ Hiver
  • - 26, 2026 17:19:19 + 31, 2026 16:45:26
  • @@ -84,7 +84,7 @@

    passTest

    -

    17:19:19 / 0.023 secs

    +

    16:45:26 / 0.024 secs

    @@ -92,9 +92,9 @@
    #test-id=1
    passTest
    -07.26.2026 17:19:19 -07.26.2026 17:19:19 -0.023 secs +07.31.2026 16:45:26 +07.31.2026 16:45:26 +0.024 secs
    @@ -104,7 +104,7 @@ Pass - 17:19:19 + 16:45:26 Test passed @@ -128,13 +128,13 @@

    Started

    -

    26, 2026 17:19:19

    +

    31, 2026 16:45:26

    Ended

    -

    26, 2026 17:19:19

    +

    31, 2026 16:45:26

    diff --git a/hiver-core/src/main/java/cc/hiver/core/dao/UserDao.java b/hiver-core/src/main/java/cc/hiver/core/dao/UserDao.java index 5847ccfe..61a762f3 100644 --- a/hiver-core/src/main/java/cc/hiver/core/dao/UserDao.java +++ b/hiver-core/src/main/java/cc/hiver/core/dao/UserDao.java @@ -119,6 +119,10 @@ public interface UserDao extends HiverBaseDao { @Query("update User u set u.mobile=?1 where u.id=?2") void updateUserPhoneById(String userPhone, String userId); + @Modifying + @Query("update User u set u.nickname=?2, u.avatar=?3 where u.id=?1") + int updateMiniProfile(String userId, String nickname, String avatar); + @Modifying @Query(value = "update t_user set saved_amount = coalesce(saved_amount, 0) + ?2 where id = ?1", nativeQuery = true) int increaseSavedAmount(String userId, BigDecimal savedAmount); diff --git a/hiver-core/src/main/java/cc/hiver/core/dao/WorkerDao.java b/hiver-core/src/main/java/cc/hiver/core/dao/WorkerDao.java index 4264f859..1284d127 100644 --- a/hiver-core/src/main/java/cc/hiver/core/dao/WorkerDao.java +++ b/hiver-core/src/main/java/cc/hiver/core/dao/WorkerDao.java @@ -23,6 +23,10 @@ public interface WorkerDao extends HiverBaseDao { @Query("update Worker w set w.isOnLine=?2 where w.workerId=?1") void changeOnLine(String id, Integer onLine); + @Modifying + @Query("update Worker w set w.isFullTime=?2 where w.workerId=?1") + void changeFullTime(String id, Integer isFullTime); + @Modifying @Query("update Worker w set w.workerStatus=?2 where w.workerId = ?1") void delById(String id, Integer workerStatusDisable); diff --git a/hiver-core/src/main/java/cc/hiver/core/entity/Worker.java b/hiver-core/src/main/java/cc/hiver/core/entity/Worker.java index 3bc7ddea..ff430643 100644 --- a/hiver-core/src/main/java/cc/hiver/core/entity/Worker.java +++ b/hiver-core/src/main/java/cc/hiver/core/entity/Worker.java @@ -199,6 +199,10 @@ public class Worker implements Serializable { @Column(name = "get_push_order") private Integer getPushOrder; + @ApiModelProperty(value = "是否全职配送员:0否 1是") + @Column(name = "is_full_time") + private Integer isFullTime = 0; + @ApiModelProperty(value = "评分") @Column(name = "score") private BigDecimal score; diff --git a/hiver-core/src/main/java/cc/hiver/core/service/UserService.java b/hiver-core/src/main/java/cc/hiver/core/service/UserService.java index 65d61a48..8b582b47 100644 --- a/hiver-core/src/main/java/cc/hiver/core/service/UserService.java +++ b/hiver-core/src/main/java/cc/hiver/core/service/UserService.java @@ -146,6 +146,8 @@ public interface UserService extends HiverBaseService { void updateUserPhoneById(String userPhone, String userId); + int updateMiniProfile(String userId, String nickname, String avatar); + /** * 判断指定用户是否已完成平台实名认证。 * diff --git a/hiver-core/src/main/java/cc/hiver/core/service/WorkerService.java b/hiver-core/src/main/java/cc/hiver/core/service/WorkerService.java index 497cbf92..c0d71375 100644 --- a/hiver-core/src/main/java/cc/hiver/core/service/WorkerService.java +++ b/hiver-core/src/main/java/cc/hiver/core/service/WorkerService.java @@ -54,6 +54,8 @@ public interface WorkerService extends HiverBaseService { */ void offLine(String id); + void changeFullTime(String id, Integer isFullTime); + /** * 删除(修改为禁用状态) * diff --git a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/UserServiceImpl.java b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/UserServiceImpl.java index 723d2944..fef22566 100644 --- a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/UserServiceImpl.java +++ b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/UserServiceImpl.java @@ -369,6 +369,11 @@ public class UserServiceImpl implements UserService { userDao.updateUserPhoneById(userPhone, userId); } + @Override + public int updateMiniProfile(String userId, String nickname, String avatar) { + return userDao.updateMiniProfile(userId, nickname, avatar); + } + @Override public boolean isRealNameVerified(String userId) { if (CharSequenceUtil.isBlank(userId)) { diff --git a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/WorkerServiceImpl.java b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/WorkerServiceImpl.java index f6f38a98..b7e74f94 100644 --- a/hiver-core/src/main/java/cc/hiver/core/serviceimpl/WorkerServiceImpl.java +++ b/hiver-core/src/main/java/cc/hiver/core/serviceimpl/WorkerServiceImpl.java @@ -248,6 +248,11 @@ public class WorkerServiceImpl implements WorkerService { workerDao.changeOnLine(id, CommonConstant.OFF_LINE); } + @Override + public void changeFullTime(String id, Integer isFullTime) { + workerDao.changeFullTime(id, isFullTime); + } + /** * 删除(修改为禁用状态) * diff --git a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java index f1bcf7eb..a6490f05 100644 --- a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java +++ b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/AuthController.java @@ -201,7 +201,7 @@ public class AuthController { } Worker worker = workerService.findByUserId(user.getId()); //更新配送员 推送id - if(worker!= null){ + if(worker!= null && StringUtils.isNotEmpty(clientId)){ worker.setClientId(clientId); workerService.update(worker); String oldRegion = worker.getRegion(); @@ -247,8 +247,9 @@ public class AuthController { @RequestMapping(value = "/smsLogin", method = RequestMethod.POST) @SystemLog(description = "短信登录", type = LogType.LOGIN) @ApiOperation("短信登录") - @Transactional(readOnly = true) + @Transactional public Result smsLogin(@RequestParam String mobile, + @RequestParam(value = "clientId", required = false) String clientId, @RequestParam(required = false) String type, @RequestParam(required = false) Boolean saveLogin) { @@ -264,27 +265,73 @@ public class AuthController { // 返回当前登录人的信息 result.put("user", user); - // 店铺登录的话,根据登录人查询所有关联的店铺(店铺启用,并且在有效期内) - if (UserConstant.USER_TYPE_NORMAL.equals(type)) { - // 获取 - final List shopUsers = shopUserService.selectByUserId(user.getId()); - if (!shopUsers.isEmpty()) { - // 获取店主手机号 - for (ShopUser shopUser : shopUsers) { - final Shop shop = shopService.findById(shopUser.getShop().getId()); - if (shop != null && shop.getShopOwnerId() != null && StringUtils.isNotEmpty(shop.getShopOwnerId())) { - final User shopOwner = userService.findById(shop.getShopOwnerId()); - if(shopOwner != null){ - shopUser.setShopOwnerName(shopOwner.getNickname()); - shopUser.setShopOwnerPhone(shopOwner.getMobile()); + List shop = shopService.getShopByUserid(user.getId()); + if(!shop.isEmpty()){ + List shopIds = new ArrayList<>(); + for (Shop item : shop) { + shopIds.add(item.getId()); + if (StringUtils.isNotEmpty(clientId)) { + item.setClientId(clientId); + shopService.update(item); + shopService.refreshShopCache(item.getId(), item.getRegionId()); + } + } + if(!shopIds.isEmpty()){ + List shopTakeaway = shopTakeawayService.selectListByshopId(shopIds); + if(shopTakeaway != null){ + shop.forEach(e -> { + for(ShopTakeaway shopTakeawa : shopTakeaway) { + if (e.getId().equals(shopTakeawa.getShopId())) { + e.setShopTakeaway(shopTakeawa); + } } + }); + } + } + result.put("shop", shop); + } + Worker worker = workerService.findByUserId(user.getId()); + //更新配送员 推送id + if(worker!= null && StringUtils.isNotEmpty(clientId)){ + worker.setClientId(clientId); + workerService.update(worker); + String oldRegion = worker.getRegion(); + WorkerRedisVo workerRedisVo = StrUtil.isNotBlank(oldRegion) ? workerRedisCacheUtil.get(oldRegion, worker.getWorkerId()) : null; + if(workerRedisVo != null){ + workerRedisVo.setWorker(worker); + if (Objects.equals(oldRegion, worker.getRegion())) { + workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo); + } else { + workerRedisCacheUtil.remove(oldRegion, worker.getWorkerId()); + if (StrUtil.isNotBlank(worker.getRegion())) { + workerRedisCacheUtil.put(worker.getRegion(), workerRedisVo); } } - result.put("shopList", shopUsers); - } else { - return ResultUtil.error("店铺未开通!"); } } + if(worker!= null && worker.getGetPushOrder() != null && worker.getGetPushOrder() == 1){ + final List workerRelaPriceList = workerRelaPriceService.selectByWorkerId(worker.getWorkerId()); + List workerRelaPriceListWaimai = new ArrayList<>(); + List workerRelaPriceListKuaidi = new ArrayList<>(); + if(workerRelaPriceList != null && workerRelaPriceList.size() > 0){ + workerRelaPriceList.forEach(workerRelaPrice -> { + if(workerRelaPrice.getGetPushOrder() == 1){ + if(workerRelaPrice.getOrderType() == 0 ){ + workerRelaPriceListWaimai.add(workerRelaPrice); + }else{ + workerRelaPriceListKuaidi.add(workerRelaPrice); + } + } + }); + } + if(workerRelaPriceListWaimai.size() > 0){ + result.put("waimaiData", workerRelaPriceListWaimai); + } + if(workerRelaPriceListKuaidi.size() > 0){ + result.put("kuaidiData", workerRelaPriceListKuaidi); + } + } + result.put("worker", worker); return ResultUtil.data(result); } diff --git a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java index 01cf7a64..89b9e1eb 100644 --- a/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java +++ b/hiver-modules/hiver-base/src/main/java/cc/hiver/base/controller/manage/UserController.java @@ -179,6 +179,53 @@ public class UserController { return ResultUtil.success("修改成功"); } + @RequestMapping(value = "/profile", method = RequestMethod.POST) + @ApiOperation(value = "小程序修改头像昵称", notes = "头像和昵称来自微信头像昵称填写能力,微信侧已完成安全检测") + public Result updateMiniProfile(@RequestBody Map body) { + final User old = securityUtil.getCurrUser(); + String nickname = body == null ? "" : StringUtils.trimToEmpty(body.get("nickname")); + String avatar = body == null ? "" : StringUtils.trimToEmpty(body.get("avatar")); + if (StringUtils.isBlank(nickname)) { + return ResultUtil.error("昵称不能为空"); + } + if (nickname.length() > 20) { + return ResultUtil.error("昵称长度不能超过20"); + } + + old.setNickname(nickname); + if (StringUtils.isNotBlank(avatar)) { + old.setAvatar(avatar); + } + userService.update(old); + entityManager.detach(old); + old.setPassword(null); + return new ResultUtil().setData(old); + } + + @RequestMapping(value = "/miniProfile", method = RequestMethod.POST) + @ApiOperation(value = "小程序只修改头像昵称", notes = "仅更新user表nickname和avatar两个字段") + public Result> updateMiniProfileOnly(@RequestBody Map body) { + final User old = securityUtil.getCurrUser(); + String nickname = body == null ? "" : StringUtils.trimToEmpty(body.get("nickname")); + String avatar = body == null ? "" : StringUtils.trimToEmpty(body.get("avatar")); + if (StringUtils.isBlank(nickname)) { + return ResultUtil.error("昵称不能为空"); + } + if (nickname.length() > 20) { + return ResultUtil.error("昵称长度不能超过20"); + } + if (StringUtils.isBlank(avatar)) { + return ResultUtil.error("头像不能为空"); + } + + userService.updateMiniProfile(old.getId(), nickname, avatar); + Map result = new HashMap<>(); + result.put("id", old.getId()); + result.put("nickname", nickname); + result.put("avatar", avatar); + return new ResultUtil>().setData(result); + } + /** * 线上demo不允许测试账号改密码 * diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusAdminController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusAdminController.java new file mode 100644 index 00000000..facf6096 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusAdminController.java @@ -0,0 +1,164 @@ +package cc.hiver.mall.campus.controller; + +import cc.hiver.core.common.utils.ResultUtil; +import cc.hiver.core.common.utils.SecurityUtil; +import cc.hiver.core.common.vo.Result; +import cc.hiver.core.entity.User; +import cc.hiver.mall.campus.dto.CampusAdminActionDTO; +import cc.hiver.mall.campus.dto.CampusPageQuery; +import cc.hiver.mall.campus.entity.*; +import cc.hiver.mall.campus.service.CampusService; +import cc.hiver.mall.ie.entity.IeContentAuditLog; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +@RestController +@Api(tags = "校园圈后台管理接口") +@RequestMapping("/hiver/app/campus/admin") +public class CampusAdminController { + @Autowired + private CampusService campusService; + @Autowired + private SecurityUtil securityUtil; + + @RequestMapping(value = "/categories", method = RequestMethod.GET) + @ApiOperation("后台查询分类与热门标签") + public Result> categories(@RequestParam(required = false) String regionId, + @RequestParam(required = false) String parentCode, + @RequestParam(required = false) String categoryType, + @RequestParam(required = false) String keyword, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "10") Integer pageSize) { + User admin = currentUser(); + String currentRegionId = regionId == null || regionId.trim().isEmpty() ? admin.getDepartmentId() : regionId; + CampusPageQuery query = new CampusPageQuery(); + query.setRegionId(currentRegionId); + query.setParentCode(parentCode); + query.setCategoryType(categoryType); + query.setKeyword(keyword); + query.setPageNum(pageNum); + query.setPageSize(pageSize); + return new ResultUtil>().setData(campusService.adminPageCategories(query)); + } + + @RequestMapping(value = "/categories", method = RequestMethod.POST) + @ApiOperation("新增/编辑分类与热门标签") + public Result saveCategory(@RequestBody CampusCategory category) { + return new ResultUtil().setData(campusService.saveCategory(category, currentUser())); + } + + @RequestMapping(value = "/categories/status", method = RequestMethod.POST) + @ApiOperation("上下架分类与热门标签") + public Result updateCategoryStatus(@RequestBody CampusAdminActionDTO dto) { + campusService.updateCategoryStatus(dto, currentUser()); + return ResultUtil.success("处理成功"); + } + + @RequestMapping(value = "/categories/delete", method = RequestMethod.POST) + @ApiOperation("删除分类与热门标签") + public Result deleteCategoryPost(@RequestParam String id) { + campusService.deleteCategory(id, currentUser()); + return ResultUtil.success("删除成功"); + } + + @RequestMapping(value = "/posts/page", method = RequestMethod.POST) + @ApiOperation("后台分页查询帖子") + public Result> pagePosts(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.pagePosts(query, null)); + } + + @RequestMapping(value = "/wall/posts/page", method = RequestMethod.POST) + @ApiOperation("后台分页查询校园墙帖子") + public Result> wallPosts(@RequestBody(required = false) CampusPageQuery query) { + if (query == null) query = new CampusPageQuery(); + query.setPostType("wall"); + return new ResultUtil>().setData(campusService.pagePosts(query, null)); + } + + @RequestMapping(value = "/partner/posts/page", method = RequestMethod.POST) + @ApiOperation("后台分页查询搭子帖子") + public Result> partnerPosts(@RequestBody(required = false) CampusPageQuery query) { + if (query == null) query = new CampusPageQuery(); + query.setPostType("partner"); + return new ResultUtil>().setData(campusService.pagePosts(query, null)); + } + + @RequestMapping(value = "/posts/status", method = RequestMethod.POST) + @ApiOperation("审核/下架/恢复/删除帖子") + public Result updatePostStatus(@RequestBody CampusAdminActionDTO dto) { + campusService.updatePostStatus(dto, currentUser()); + return ResultUtil.success("处理成功"); + } + + @RequestMapping(value = "/posts/{id}/comments/page", method = RequestMethod.GET) + @ApiOperation("后台查询帖子评论") + public Result> postComments(@PathVariable String id, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "10") Integer pageSize) { + return new ResultUtil>().setData(campusService.adminPageComments(id, pageNum, pageSize)); + } + + @RequestMapping(value = "/comments/{id}/delete", method = RequestMethod.POST) + @ApiOperation("后台删除评论") + public Result deleteComment(@PathVariable String id) { + campusService.adminDeleteComment(id, currentUser()); + return ResultUtil.success("删除成功"); + } + + @RequestMapping(value = "/complaints/page", method = RequestMethod.POST) + @ApiOperation("举报分页") + public Result> complaints(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.adminPageComplaints(query)); + } + + @RequestMapping(value = "/complaints/handle", method = RequestMethod.POST) + @ApiOperation("处理举报") + public Result handleComplaint(@RequestBody CampusAdminActionDTO dto) { + campusService.handleComplaint(dto, currentUser()); + return ResultUtil.success("处理成功"); + } + + @RequestMapping(value = "/complaints/unblock", method = RequestMethod.POST) + @ApiOperation("解除封禁") + public Result unblockComplaintUser(@RequestBody CampusAdminActionDTO dto) { + campusService.unblockComplaintUser(dto, currentUser()); + return ResultUtil.success("解除封禁成功"); + } + + @RequestMapping(value = "/users/limits/page", method = RequestMethod.POST) + @ApiOperation("用户风控分页") + public Result> userLimits(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.adminPageUserLimits(query)); + } + + @RequestMapping(value = "/users/limits", method = RequestMethod.POST) + @ApiOperation("保存用户风控") + public Result saveUserLimit(@RequestBody CampusAdminActionDTO dto) { + return new ResultUtil().setData(campusService.saveUserLimit(dto, currentUser())); + } + + @RequestMapping(value = "/logs/page", method = RequestMethod.POST) + @ApiOperation("后台操作日志分页") + public Result> logs(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.adminPageLogs(query)); + } + + @RequestMapping(value = "/audit-logs/page", method = RequestMethod.GET) + @ApiOperation("校园圈违规内容分页") + public Result> auditLogs(@RequestParam(required = false) String regionId, + @RequestParam(required = false, defaultValue = "1") Integer pageNumber, + @RequestParam(required = false, defaultValue = "10") Integer pageSize) { + return new ResultUtil>().setData(campusService.adminAuditLogs(regionId, pageNumber, pageSize)); + } + + private User currentUser() { + User user = securityUtil.getCurrUser(); + if (user == null) { + throw new RuntimeException("请先登录"); + } + return user; + } +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusController.java new file mode 100644 index 00000000..017a996b --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/controller/CampusController.java @@ -0,0 +1,224 @@ +package cc.hiver.mall.campus.controller; + +import cc.hiver.core.common.utils.ResultUtil; +import cc.hiver.core.common.utils.SecurityUtil; +import cc.hiver.core.common.vo.Result; +import cc.hiver.core.entity.User; +import cc.hiver.mall.campus.dto.*; +import cc.hiver.mall.campus.entity.*; +import cc.hiver.mall.campus.service.CampusService; +import cc.hiver.mall.campus.vo.CampusPostDetailVO; +import com.baomidou.mybatisplus.core.metadata.IPage; +import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.*; + +import java.util.List; +import java.util.Map; + +@RestController +@Api(tags = "校园圈用户端接口") +@RequestMapping("/hiver/app/campus") +public class CampusController { + @Autowired + private CampusService campusService; + @Autowired + private SecurityUtil securityUtil; + + @RequestMapping(value = "/categories", method = RequestMethod.GET) + @ApiOperation("查询校园圈分类和热门标签") + public Result> categories(@RequestParam(required = false) String regionId, + @RequestParam(required = false) String parentCode, + @RequestParam(required = false) String categoryType) { + return new ResultUtil>().setData(campusService.listCategories(regionId, parentCode, categoryType)); + } + + @RequestMapping(value = "/posts/page", method = RequestMethod.POST) + @ApiOperation("分页查询校园圈帖子") + public Result> pagePosts(@RequestBody(required = false) CampusPageQuery query) { + if (query == null) query = new CampusPageQuery(); + if (query.getStatus() == null) query.setStatus(1); + if (query.getAuditStatus() == null) query.setAuditStatus(1); + return new ResultUtil>().setData(campusService.pagePosts(query, currentUserOrNull())); + } + + @RequestMapping(value = "/partner/posts/page", method = RequestMethod.POST) + @ApiOperation("分页查询找搭子") + public Result> pagePartnerPosts(@RequestBody(required = false) CampusPageQuery query) { + if (query == null) query = new CampusPageQuery(); + query.setPostType("partner"); + if (query.getStatus() == null) query.setStatus(1); + if (query.getAuditStatus() == null) query.setAuditStatus(1); + return new ResultUtil>().setData(campusService.pagePosts(query, currentUserOrNull())); + } + + @RequestMapping(value = "/partner/posts/match", method = RequestMethod.POST) + @ApiOperation("按分类和标题模糊匹配找搭子") + public Result matchPartnerPost(@RequestBody(required = false) CampusPageQuery query) { + if (query == null) query = new CampusPageQuery(); + query.setPostType("partner"); + query.setStatus(1); + query.setAuditStatus(1); + return new ResultUtil().setData(campusService.matchPartnerPost(query, currentUserOrNull())); + } + + @RequestMapping(value = "/posts/{id}", method = RequestMethod.GET) + @ApiOperation("查询校园圈帖子详情") + public Result detail(@PathVariable String id) { + return new ResultUtil().setData(campusService.postDetail(id, currentUserOrNull())); + } + + @RequestMapping(value = "/posts", method = RequestMethod.POST) + @ApiOperation("发布/编辑校园圈内容") + public Result publish(@RequestBody CampusPostDTO dto) { + return new ResultUtil().setData(campusService.publishPost(dto, currentUser())); + } + + @RequestMapping(value = "/images/audit", method = RequestMethod.POST) + @ApiOperation("校园圈图片审核") + public Result> auditReleaseImage(@RequestBody Map body) { + return new ResultUtil>().setData(campusService.auditReleaseImage(body == null ? "" : body.get("url"), currentUser())); + } + + @RequestMapping(value = "/like", method = RequestMethod.POST) + @ApiOperation("点赞/表情反应") + public Result> like(@RequestParam String targetType, + @RequestParam String targetId, + @RequestParam(required = false) String reaction) { + return new ResultUtil>().setData(campusService.toggleLike(targetType, targetId, reaction, currentUser())); + } + + @RequestMapping(value = "/comments", method = RequestMethod.POST) + @ApiOperation("发表评论/回复") + public Result addComment(@RequestBody CampusCommentDTO dto) { + return new ResultUtil().setData(campusService.addComment(dto, currentUser())); + } + + @RequestMapping(value = "/comments/page", method = RequestMethod.GET) + @ApiOperation("分页查询评论") + public Result> pageComments(@RequestParam String postId, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "10") Integer pageSize) { + return new ResultUtil>().setData(campusService.pageComments(postId, pageNum, pageSize, currentUserOrNull())); + } + + @RequestMapping(value = "/comments/{id}", method = RequestMethod.DELETE) + @ApiOperation("删除自己的评论") + public Result deleteComment(@PathVariable String id) { + campusService.deleteComment(id, currentUser()); + return ResultUtil.success("删除成功"); + } + + @RequestMapping(value = "/posts/{postId}/chat", method = RequestMethod.POST) + @ApiOperation("创建校园圈聊天会话") + public Result chat(@PathVariable String postId) { + return new ResultUtil().setData(campusService.createChatSession(postId, currentUser())); + } + + @RequestMapping(value = "/chat/sessions/{sessionId}", method = RequestMethod.GET) + @ApiOperation("查询校园圈聊天会话") + public Result chatSession(@PathVariable String sessionId) { + return new ResultUtil().setData(campusService.getChatSession(sessionId, currentUser())); + } + + @RequestMapping(value = "/chat/sessions/{sessionId}/messages/page", method = RequestMethod.GET) + @ApiOperation("分页查询校园圈聊天消息") + public Result> chatMessages(@PathVariable String sessionId, + @RequestParam(required = false, defaultValue = "1") Integer pageNum, + @RequestParam(required = false, defaultValue = "20") Integer pageSize) { + return new ResultUtil>().setData(campusService.pageChatMessages(sessionId, pageNum, pageSize, currentUser())); + } + + @RequestMapping(value = "/chat/messages", method = RequestMethod.POST) + @ApiOperation("发送校园圈聊天消息") + public Result sendChatMessage(@RequestBody CampusChatMessageDTO dto) { + return new ResultUtil().setData(campusService.sendChatMessage(dto, currentUser())); + } + + @RequestMapping(value = "/complaints", method = RequestMethod.POST) + @ApiOperation("提交举报") + public Result complaint(@RequestBody CampusComplaintDTO dto) { + return new ResultUtil().setData(campusService.submitComplaint(dto, currentUser())); + } + + @RequestMapping(value = "/my/posts", method = RequestMethod.POST) + @ApiOperation("我的校园圈发布") + public Result> myPosts(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.pageMyPosts(query, currentUser())); + } + + @RequestMapping(value = "/my/posts/status", method = RequestMethod.POST) + @ApiOperation("上下架/删除自己的校园圈发布") + public Result updateMyPostStatus(@RequestBody CampusAdminActionDTO dto) { + campusService.updateMyPostStatus(dto, currentUser()); + return ResultUtil.success("处理成功"); + } + + @RequestMapping(value = "/my/sessions", method = RequestMethod.POST) + @ApiOperation("我的校园圈会话") + public Result> mySessions(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.pageMySessions(query, currentUser())); + } + + @RequestMapping(value = "/my/sessions/{id}", method = RequestMethod.DELETE) + @ApiOperation("删除我的校园圈会话") + public Result deleteMySessionMessage(@PathVariable String id) { + campusService.deleteMySessionMessage(id, currentUser()); + return ResultUtil.success("删除成功"); + } + + @RequestMapping(value = "/my/message-comments", method = RequestMethod.POST) + @ApiOperation("我的校园圈留言消息") + public Result> myMessageComments(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.pageMyMessageComments(query, currentUser())); + } + + @RequestMapping(value = "/my/comments", method = RequestMethod.POST) + @ApiOperation("我的校园圈评论") + public Result> myComments(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.pageMyComments(query, currentUser())); + } + + @RequestMapping(value = "/my/message-comments/{id}", method = RequestMethod.DELETE) + @ApiOperation("删除我的校园圈留言消息") + public Result deleteMyCommentMessage(@PathVariable String id) { + campusService.deleteMyCommentMessage(id, currentUser()); + return ResultUtil.success("删除成功"); + } + + @RequestMapping(value = "/my/complaints", method = RequestMethod.POST) + @ApiOperation("我的校园圈举报") + public Result> myComplaints(@RequestBody(required = false) CampusPageQuery query) { + return new ResultUtil>().setData(campusService.pageMyComplaints(query, currentUser())); + } + + @RequestMapping(value = "/unread/summary", method = RequestMethod.GET) + @ApiOperation("校园圈未读汇总") + public Result> unreadSummary() { + return new ResultUtil>().setData(campusService.unreadSummary(currentUser())); + } + + @RequestMapping(value = "/unread/read-all", method = RequestMethod.POST) + @ApiOperation("校园圈一键已读") + public Result markAllUnreadRead() { + campusService.markAllUnreadRead(currentUser()); + return ResultUtil.success("处理成功"); + } + + private User currentUser() { + User user = securityUtil.getCurrUser(); + if (user == null) { + throw new RuntimeException("请先登录"); + } + return user; + } + + private User currentUserOrNull() { + try { + return securityUtil.getCurrUser(); + } catch (Exception e) { + return null; + } + } +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusAdminActionDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusAdminActionDTO.java new file mode 100644 index 00000000..9d7c5178 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusAdminActionDTO.java @@ -0,0 +1,24 @@ +package cc.hiver.mall.campus.dto; + +import lombok.Data; + +import java.util.Date; + +@Data +public class CampusAdminActionDTO { + private String id; + private String postId; + private String commentId; + private String complaintId; + private String userId; + private Integer status; + private Integer auditStatus; + private String reason; + private String handleResult; + private Boolean blockUser; + private Integer muted; + private Integer publishBanned; + private Integer permanentBanned; + private Date startTime; + private Date endTime; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusChatMessageDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusChatMessageDTO.java new file mode 100644 index 00000000..20697ec7 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusChatMessageDTO.java @@ -0,0 +1,12 @@ +package cc.hiver.mall.campus.dto; + +import lombok.Data; + +@Data +public class CampusChatMessageDTO { + private String sessionId; + /** 1文本,2图片 */ + private Integer messageType; + private String content; + private String clientMsgId; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusCommentDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusCommentDTO.java new file mode 100644 index 00000000..b6ba0f29 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusCommentDTO.java @@ -0,0 +1,14 @@ +package cc.hiver.mall.campus.dto; + +import lombok.Data; + +@Data +public class CampusCommentDTO { + private String postId; + private String userName; + private String userAvatar; + private String parentId; + private String replyToUserId; + private String replyToUserName; + private String content; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusComplaintDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusComplaintDTO.java new file mode 100644 index 00000000..b1780a26 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusComplaintDTO.java @@ -0,0 +1,15 @@ +package cc.hiver.mall.campus.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class CampusComplaintDTO { + private String postId; + private String commentId; + private String sessionId; + private String complaintType; + private String reason; + private List images; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPageQuery.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPageQuery.java new file mode 100644 index 00000000..8d65d883 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPageQuery.java @@ -0,0 +1,25 @@ +package cc.hiver.mall.campus.dto; + +import lombok.Data; + +@Data +public class CampusPageQuery { + private Integer pageNum = 1; + private Integer pageSize = 10; + private String keyword; + private String regionId; + private String userId; + private String postType; + private String partnerType; + private String parentCode; + private String categoryType; + private String categoryId; + private String tag; + private String statusText; + private Integer auditStatus; + private Integer status; + private String targetType; + private String type; + /** hot/latest/match */ + private String sortType; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPostDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPostDTO.java new file mode 100644 index 00000000..dbdb5811 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/dto/CampusPostDTO.java @@ -0,0 +1,28 @@ +package cc.hiver.mall.campus.dto; + +import lombok.Data; + +import java.util.List; + +@Data +public class CampusPostDTO { + private String id; + private String regionId; + private String regionName; + private String userName; + private String userAvatar; + private String postType; + private String partnerType; + private String categoryId; + private String categoryName; + private String title; + private String content; + private String contentText; + private List images; + private List tags; + private String location; + private String activityTime; + private Integer targetCount; + private String contactWechat; + private String contactPhone; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusAdminLog.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusAdminLog.java new file mode 100644 index 00000000..dec8cc3b --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusAdminLog.java @@ -0,0 +1,31 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "校园圈后台操作日志") +@TableName(value = "campus_admin_log", autoResultMap = true) +public class CampusAdminLog extends HiverBaseEntity { + @ApiModelProperty("校区/区域ID") + private String regionId; + @ApiModelProperty("管理员ID") + private String adminId; + @ApiModelProperty("管理员昵称") + private String adminName; + @ApiModelProperty("操作类型") + private String actionType; + @ApiModelProperty("目标类型") + private String targetType; + @ApiModelProperty("目标ID") + private String targetId; + @ApiModelProperty("原因") + private String reason; + @ApiModelProperty("变更前") + private String beforeData; + @ApiModelProperty("变更后") + private String afterData; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusCategory.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusCategory.java new file mode 100644 index 00000000..5557888d --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusCategory.java @@ -0,0 +1,35 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "校园圈分类与热门标签") +@TableName(value = "campus_category", autoResultMap = true) +public class CampusCategory extends HiverBaseEntity { + @ApiModelProperty("校区/区域ID") + private String regionId; + @ApiModelProperty("校区/区域名称") + private String regionName; + @ApiModelProperty("一级频道:wall/game/daily/sport") + private String parentCode; + @ApiModelProperty("分类或标签编码") + private String code; + @ApiModelProperty("分类或标签名称") + private String name; + @ApiModelProperty("描述") + private String description; + @ApiModelProperty("图标") + private String icon; + @ApiModelProperty("颜色") + private String color; + @ApiModelProperty("category分类,tag热门标签") + private String categoryType; + @ApiModelProperty("排序") + private Integer sortOrder; + @ApiModelProperty("状态:1启用,2停用") + private Integer status; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatMessage.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatMessage.java new file mode 100644 index 00000000..94737138 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatMessage.java @@ -0,0 +1,38 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "校园圈聊天消息") +@TableName(value = "campus_chat_message", autoResultMap = true) +public class CampusChatMessage extends HiverBaseEntity { + @ApiModelProperty("业务会话ID") + private String sessionId; + @ApiModelProperty("帖子ID") + private String postId; + @ApiModelProperty("发送人ID") + private String senderId; + @ApiModelProperty("接收人ID") + private String receiverId; + @ApiModelProperty("1文本,2图片") + private Integer messageType; + @ApiModelProperty("消息内容") + private String content; + @ApiModelProperty("审核状态:0待审核,1通过,2拒绝") + private Integer auditStatus; + @ApiModelProperty("审核原因") + private String auditReason; + @ApiModelProperty("状态:1正常,2撤回/删除") + private Integer status; + @ApiModelProperty("是否已读:0否,1是") + private Integer readFlag; + + @TableField(exist = false) + @ApiModelProperty("小程序端本地消息ID") + private String clientMsgId; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatSession.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatSession.java new file mode 100644 index 00000000..965c8ca0 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusChatSession.java @@ -0,0 +1,50 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "校园圈聊天会话") +@TableName(value = "campus_chat_session", autoResultMap = true) +public class CampusChatSession extends HiverBaseEntity { + @ApiModelProperty("业务会话ID") + private String sessionId; + @ApiModelProperty("帖子ID") + private String postId; + @ApiModelProperty("发布人ID") + private String ownerId; + @ApiModelProperty("联系用户ID") + private String peerUserId; + @ApiModelProperty("最后消息时间") + private Date lastMessageTime; + @ApiModelProperty("最后消息摘要") + private String lastMessage; + @ApiModelProperty("是否结束") + private Integer ended; + @ApiModelProperty("发布人是否删除列表记录") + private Integer ownerDeleted; + @ApiModelProperty("联系用户是否删除列表记录") + private Integer peerDeleted; + + @TableField(exist = false) + @ApiModelProperty("帖子标题") + private String displayTitle; + @TableField(exist = false) + @ApiModelProperty("帖子封面") + private String displayCover; + @TableField(exist = false) + @ApiModelProperty("帖子类型") + private String postType; + @TableField(exist = false) + @ApiModelProperty("搭子类型") + private String partnerType; + @TableField(exist = false) + @ApiModelProperty("当前用户未读消息数") + private Integer unreadCount; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComment.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComment.java new file mode 100644 index 00000000..3816aaa1 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComment.java @@ -0,0 +1,53 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.List; + +@Data +@ApiModel(value = "校园圈评论") +@TableName(value = "campus_comment", autoResultMap = true) +public class CampusComment extends HiverBaseEntity { + @ApiModelProperty("帖子ID") + private String postId; + @ApiModelProperty("父评论ID") + private String parentId; + @ApiModelProperty("评论人ID") + private String userId; + @ApiModelProperty("评论人昵称") + private String userName; + @ApiModelProperty("评论人头像") + private String userAvatar; + @ApiModelProperty("回复目标用户ID") + private String replyToUserId; + @ApiModelProperty("回复目标用户昵称") + private String replyToUserName; + @ApiModelProperty("评论内容") + private String content; + @ApiModelProperty("点赞数") + private Integer likeCount; + @ApiModelProperty("审核状态:0待审核,1通过,2拒绝") + private Integer auditStatus; + @ApiModelProperty("审核原因") + private String auditReason; + @ApiModelProperty("状态:1正常,2删除") + private Integer status; + + @TableField(exist = false) + @ApiModelProperty("回复列表") + private List replies; + @TableField(exist = false) + @ApiModelProperty("当前用户未读数") + private Integer unreadCount; + @TableField(exist = false) + @ApiModelProperty("帖子标题") + private String displayTitle; + @TableField(exist = false) + @ApiModelProperty("帖子封面") + private String displayCover; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComplaint.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComplaint.java new file mode 100644 index 00000000..0c230741 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusComplaint.java @@ -0,0 +1,59 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "校园圈投诉举报") +@TableName(value = "campus_complaint", autoResultMap = true) +public class CampusComplaint extends HiverBaseEntity { + @ApiModelProperty("校区/区域ID") + private String regionId; + @ApiModelProperty("举报人ID") + private String reporterId; + @ApiModelProperty("举报人昵称") + private String reporterName; + @ApiModelProperty("帖子ID") + private String postId; + @ApiModelProperty("评论ID") + private String commentId; + @ApiModelProperty("会话ID") + private String sessionId; + @ApiModelProperty("被举报用户ID") + private String targetUserId; + @ApiModelProperty("post/comment/chat") + private String complaintType; + @ApiModelProperty("举报原因") + private String reason; + @ApiModelProperty("举报图片JSON") + private String images; + @ApiModelProperty("0待处理,1通过,2驳回") + private Integer status; + @ApiModelProperty("处理结果") + private String handleResult; + @ApiModelProperty("处理管理员ID") + private String adminId; + @ApiModelProperty("处理管理员名称") + private String adminName; + @ApiModelProperty("处理时间") + private Date handleTime; + + @TableField(exist = false) + @ApiModelProperty("举报对象标题") + private String displayTitle; + @TableField(exist = false) + @ApiModelProperty("举报对象封面") + private String displayCover; + @TableField(exist = false) + @ApiModelProperty("评论内容") + private String commentContent; + @TableField(exist = false) + @ApiModelProperty("被举报用户手机号") + private String targetUserMobile; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusLike.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusLike.java new file mode 100644 index 00000000..8b058920 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusLike.java @@ -0,0 +1,23 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +@Data +@ApiModel(value = "校园圈点赞和表情反应") +@TableName(value = "campus_like", autoResultMap = true) +public class CampusLike extends HiverBaseEntity { + @ApiModelProperty("用户ID") + private String userId; + @ApiModelProperty("post/comment/reaction") + private String targetType; + @ApiModelProperty("目标ID") + private String targetId; + @ApiModelProperty("表情反应") + private String reaction; + @ApiModelProperty("1有效,2取消") + private Integer status; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusPost.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusPost.java new file mode 100644 index 00000000..3e7d948d --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusPost.java @@ -0,0 +1,107 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +@Data +@ApiModel(value = "校园圈帖子") +@TableName(value = "campus_post", autoResultMap = true) +public class CampusPost extends HiverBaseEntity { + @ApiModelProperty("校区/区域ID") + private String regionId; + @ApiModelProperty("校区/区域名称") + private String regionName; + @ApiModelProperty("发布人ID") + private String userId; + @ApiModelProperty("发布人昵称") + private String userName; + @ApiModelProperty("发布人头像") + private String userAvatar; + @ApiModelProperty("wall校园墙,partner找搭子") + private String postType; + @ApiModelProperty("game/daily/sport") + private String partnerType; + @ApiModelProperty("二级分类ID") + private String categoryId; + @ApiModelProperty("二级分类名称") + private String categoryName; + @ApiModelProperty("标题") + private String title; + @ApiModelProperty("正文") + private String content; + @ApiModelProperty("纯文本正文") + private String contentText; + @ApiModelProperty("封面图") + private String coverImage; + @ApiModelProperty("图片JSON") + private String images; + @ApiModelProperty("标签JSON") + private String tags; + @ApiModelProperty("地点") + private String location; + @ApiModelProperty("期望时间") + private String activityTime; + @ApiModelProperty("期望人数") + private Integer targetCount; + @ApiModelProperty("已联系/参与人数") + private Integer joinedCount; + @ApiModelProperty("联系方式:chat站内聊天") + private String contactType; + @ApiModelProperty("联系微信") + private String contactWechat; + @ApiModelProperty("联系电话") + private String contactPhone; + @ApiModelProperty("浏览数") + private Integer browseCount; + @ApiModelProperty("点赞数") + private Integer likeCount; + @ApiModelProperty("评论数") + private Integer commentCount; + @ApiModelProperty("会话数") + private Integer chatCount; + @ApiModelProperty("举报数") + private Integer complaintCount; + @ApiModelProperty("热度分") + private Integer hotScore; + @ApiModelProperty("业务状态:1正常,2下架,3删除") + private Integer status; + @ApiModelProperty("审核状态:0待审核,1通过,2拒绝") + private Integer auditStatus; + @ApiModelProperty("审核原因") + private String auditReason; + @ApiModelProperty("下架原因") + private String offShelfReason; + + @TableField(exist = false) + @ApiModelProperty("图片列表") + private List imageList; + @TableField(exist = false) + @ApiModelProperty("标签列表") + private List tagList; + @TableField(exist = false) + @ApiModelProperty("当前用户是否点赞") + private Boolean liked; + @TableField(exist = false) + @ApiModelProperty("当前用户是否作者") + private Boolean mine; + @TableField(exist = false) + @ApiModelProperty("最后消息/评论时间") + private Date lastActiveTime; + @TableField(exist = false) + @ApiModelProperty("表情反应数量") + private Map reactionCounts; + @TableField(exist = false) + @ApiModelProperty("当前用户未读评论数") + private Integer unreadCommentCount; + @TableField(exist = false) + @ApiModelProperty("当前用户未读聊天数") + private Integer unreadChatCount; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusUserLimit.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusUserLimit.java new file mode 100644 index 00000000..f0243578 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/entity/CampusUserLimit.java @@ -0,0 +1,33 @@ +package cc.hiver.mall.campus.entity; + +import cc.hiver.core.base.HiverBaseEntity; +import com.baomidou.mybatisplus.annotation.TableName; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; + +import java.util.Date; + +@Data +@ApiModel(value = "校园圈用户风控") +@TableName(value = "campus_user_limit", autoResultMap = true) +public class CampusUserLimit extends HiverBaseEntity { + @ApiModelProperty("校区/区域ID") + private String regionId; + @ApiModelProperty("用户ID") + private String userId; + @ApiModelProperty("用户昵称") + private String userName; + @ApiModelProperty("是否禁言") + private Integer muted; + @ApiModelProperty("是否禁止发布") + private Integer publishBanned; + @ApiModelProperty("是否永久封禁") + private Integer permanentBanned; + @ApiModelProperty("封禁原因") + private String banReason; + @ApiModelProperty("开始时间") + private Date startTime; + @ApiModelProperty("结束时间") + private Date endTime; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusAdminLogMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusAdminLogMapper.java new file mode 100644 index 00000000..ee03916b --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusAdminLogMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusAdminLog; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusAdminLogMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCategoryMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCategoryMapper.java new file mode 100644 index 00000000..3fb2aad2 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCategoryMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusCategory; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusCategoryMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatMessageMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatMessageMapper.java new file mode 100644 index 00000000..c29e6ec5 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatMessageMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusChatMessage; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusChatMessageMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatSessionMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatSessionMapper.java new file mode 100644 index 00000000..67732e58 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusChatSessionMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusChatSession; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusChatSessionMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCommentMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCommentMapper.java new file mode 100644 index 00000000..3aa895e1 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusCommentMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusComment; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusCommentMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusComplaintMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusComplaintMapper.java new file mode 100644 index 00000000..14573d6d --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusComplaintMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusComplaint; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusComplaintMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusLikeMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusLikeMapper.java new file mode 100644 index 00000000..71fa011d --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusLikeMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusLike; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusLikeMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusPostMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusPostMapper.java new file mode 100644 index 00000000..c00ab1f9 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusPostMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusPost; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusPostMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusUserLimitMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusUserLimitMapper.java new file mode 100644 index 00000000..4e214c0e --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/mapper/CampusUserLimitMapper.java @@ -0,0 +1,9 @@ +package cc.hiver.mall.campus.mapper; + +import cc.hiver.mall.campus.entity.CampusUserLimit; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Mapper; + +@Mapper +public interface CampusUserLimitMapper extends BaseMapper { +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/CampusService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/CampusService.java new file mode 100644 index 00000000..cc975cdf --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/CampusService.java @@ -0,0 +1,93 @@ +package cc.hiver.mall.campus.service; + +import cc.hiver.core.entity.User; +import cc.hiver.mall.campus.dto.*; +import cc.hiver.mall.campus.entity.*; +import cc.hiver.mall.campus.vo.CampusPostDetailVO; +import cc.hiver.mall.ie.entity.IeContentAuditLog; +import com.baomidou.mybatisplus.core.metadata.IPage; + +import java.util.List; +import java.util.Map; + +public interface CampusService { + List listCategories(String regionId, String parentCode, String categoryType); + + List adminListCategories(String regionId); + + IPage adminPageCategories(CampusPageQuery query); + + CampusCategory saveCategory(CampusCategory category, User admin); + + void updateCategoryStatus(CampusAdminActionDTO dto, User admin); + + void deleteCategory(String id, User admin); + + IPage pagePosts(CampusPageQuery query, User user); + + CampusPost matchPartnerPost(CampusPageQuery query, User user); + + IPage pageMyPosts(CampusPageQuery query, User user); + + CampusPostDetailVO postDetail(String id, User user); + + CampusPost publishPost(CampusPostDTO dto, User user); + + Map auditReleaseImage(String imageUrl, User user); + + Map toggleLike(String targetType, String targetId, String reaction, User user); + + CampusComment addComment(CampusCommentDTO dto, User user); + + IPage pageComments(String postId, Integer pageNum, Integer pageSize, User user); + + IPage pageMyMessageComments(CampusPageQuery query, User user); + + IPage pageMyComments(CampusPageQuery query, User user); + + void deleteMyCommentMessage(String commentId, User user); + + CampusChatSession createChatSession(String postId, User user); + + CampusChatSession getChatSession(String sessionId, User user); + + CampusChatMessage sendChatMessage(CampusChatMessageDTO dto, User user); + + IPage pageChatMessages(String sessionId, Integer pageNum, Integer pageSize, User user); + + IPage pageMySessions(CampusPageQuery query, User user); + + void deleteMySessionMessage(String sessionId, User user); + + CampusComplaint submitComplaint(CampusComplaintDTO dto, User user); + + IPage pageMyComplaints(CampusPageQuery query, User user); + + Map unreadSummary(User user); + + void markAllUnreadRead(User user); + + void deleteComment(String id, User user); + + void updateMyPostStatus(CampusAdminActionDTO dto, User user); + + void updatePostStatus(CampusAdminActionDTO dto, User admin); + + IPage adminPageComments(String postId, Integer pageNum, Integer pageSize); + + void adminDeleteComment(String id, User admin); + + IPage adminPageComplaints(CampusPageQuery query); + + void handleComplaint(CampusAdminActionDTO dto, User admin); + + void unblockComplaintUser(CampusAdminActionDTO dto, User admin); + + IPage adminPageUserLimits(CampusPageQuery query); + + CampusUserLimit saveUserLimit(CampusAdminActionDTO dto, User admin); + + IPage adminPageLogs(CampusPageQuery query); + + IPage adminAuditLogs(String regionId, Integer pageNumber, Integer pageSize); +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/impl/CampusServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/impl/CampusServiceImpl.java new file mode 100644 index 00000000..a5d9627b --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/service/impl/CampusServiceImpl.java @@ -0,0 +1,1438 @@ +package cc.hiver.mall.campus.service.impl; + +import cc.hiver.core.entity.User; +import cc.hiver.core.service.UserService; +import cc.hiver.mall.campus.dto.*; +import cc.hiver.mall.campus.entity.*; +import cc.hiver.mall.campus.mapper.*; +import cc.hiver.mall.campus.service.CampusService; +import cc.hiver.mall.campus.vo.CampusPostDetailVO; +import cc.hiver.mall.ie.constant.IeConstants; +import cc.hiver.mall.ie.entity.IeContentAuditLog; +import cc.hiver.mall.ie.mapper.IeContentAuditLogMapper; +import cc.hiver.mall.ie.service.IeAliyunModerationService; +import cc.hiver.mall.ie.service.IeContentAuditLogService; +import cc.hiver.mall.ie.service.IeSecurityAuditService; +import cc.hiver.mall.ie.vo.IeAliyunModerationResult; +import cc.hiver.mall.ie.vo.IeAuditResult; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.messaging.simp.SimpMessagingTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.PlatformTransactionManager; +import org.springframework.transaction.TransactionDefinition; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.function.Function; +import java.util.stream.Collectors; + +@Slf4j +@Service +public class CampusServiceImpl implements CampusService { + private static final String TEXT_AUDIT_REJECT_MESSAGE = "内容审核未通过,请修改后重试"; + private static final String IMAGE_AUDIT_REJECT_MESSAGE = "图片审核未通过,请更换后重试"; + private static final String CHAT_UNREAD_KEY_PREFIX = "campus:unread:chat:"; + private static final String COMMENT_UNREAD_KEY_PREFIX = "campus:unread:comment:"; + private static final int STATUS_NORMAL = 1; + private static final int STATUS_OFF_SHELF = 2; + private static final int STATUS_DELETED = 3; + private static final int AUDIT_PASS = 1; + + @Autowired + private CampusCategoryMapper categoryMapper; + @Autowired + private CampusPostMapper postMapper; + @Autowired + private CampusCommentMapper commentMapper; + @Autowired + private CampusLikeMapper likeMapper; + @Autowired + private CampusChatSessionMapper chatSessionMapper; + @Autowired + private CampusChatMessageMapper chatMessageMapper; + @Autowired + private CampusComplaintMapper complaintMapper; + @Autowired + private CampusUserLimitMapper userLimitMapper; + @Autowired + private CampusAdminLogMapper adminLogMapper; + @Autowired + private IeSecurityAuditService securityAuditService; + @Autowired(required = false) + private IeAliyunModerationService aliyunModerationService; + @Autowired(required = false) + private IeContentAuditLogService contentAuditLogService; + @Autowired + private IeContentAuditLogMapper auditLogMapper; + @Autowired + private UserService userService; + @Autowired(required = false) + private StringRedisTemplate stringRedisTemplate; + @Autowired(required = false) + private SimpMessagingTemplate messagingTemplate; + @Autowired + private PlatformTransactionManager transactionManager; + + @Override + public List listCategories(String regionId, String parentCode, String categoryType) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(StringUtils.isNotBlank(regionId), CampusCategory::getRegionId, regionId) + .isNull(StringUtils.isBlank(regionId), CampusCategory::getRegionId) + .eq(StringUtils.isNotBlank(parentCode), CampusCategory::getParentCode, parentCode) + .eq(StringUtils.isNotBlank(categoryType), CampusCategory::getCategoryType, categoryType) + .eq(CampusCategory::getStatus, STATUS_NORMAL) + .orderByAsc(CampusCategory::getSortOrder) + .orderByDesc(CampusCategory::getCreateTime); + return categoryMapper.selectList(wrapper); + } + + @Override + public List adminListCategories(String regionId) { + return categoryMapper.selectList(new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(regionId), CampusCategory::getRegionId, regionId) + .orderByAsc(CampusCategory::getParentCode) + .orderByAsc(CampusCategory::getCategoryType) + .orderByAsc(CampusCategory::getSortOrder)); + } + + @Override + public IPage adminPageCategories(CampusPageQuery query) { + CampusPageQuery pageQuery = normalizeQuery(query); + return categoryMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(pageQuery.getRegionId()), CampusCategory::getRegionId, pageQuery.getRegionId()) + .eq(StringUtils.isNotBlank(pageQuery.getParentCode()), CampusCategory::getParentCode, pageQuery.getParentCode()) + .eq(StringUtils.isNotBlank(pageQuery.getCategoryType()), CampusCategory::getCategoryType, pageQuery.getCategoryType()) + .like(StringUtils.isNotBlank(pageQuery.getKeyword()), CampusCategory::getName, pageQuery.getKeyword()) + .orderByAsc(CampusCategory::getParentCode) + .orderByAsc(CampusCategory::getCategoryType) + .orderByAsc(CampusCategory::getSortOrder)); + } + + @Override + @Transactional + public CampusCategory saveCategory(CampusCategory category, User admin) { + requireLogin(admin); + if (category == null || StringUtils.isAnyBlank(category.getParentCode(), category.getCode(), category.getName())) { + throw new RuntimeException("分类参数不完整"); + } + category.setCategoryType(StringUtils.defaultIfBlank(category.getCategoryType(), "category")); + category.setStatus(category.getStatus() == null ? STATUS_NORMAL : category.getStatus()); + category.setSortOrder(category.getSortOrder() == null ? 100 : category.getSortOrder()); + if (StringUtils.isBlank(category.getRegionId())) { + category.setRegionId(admin.getDepartmentId()); + category.setRegionName(admin.getDepartmentTitle()); + } + CampusCategory existed = StringUtils.isBlank(category.getId()) ? null : categoryMapper.selectById(category.getId()); + if (existed == null) { + category.setCreateBy(admin.getId()); + categoryMapper.insert(category); + logAdmin(admin, "category_create", "category", category.getId(), category.getName()); + } else { + category.setUpdateBy(admin.getId()); + categoryMapper.updateById(category); + logAdmin(admin, "category_update", "category", category.getId(), category.getName()); + } + return category; + } + + @Override + @Transactional + public void updateCategoryStatus(CampusAdminActionDTO dto, User admin) { + requireLogin(admin); + CampusCategory category = requireCategory(dto == null ? null : dto.getId()); + category.setStatus(dto.getStatus() == null ? STATUS_NORMAL : dto.getStatus()); + category.setUpdateBy(admin.getId()); + categoryMapper.updateById(category); + logAdmin(admin, "category_status", "category", category.getId(), dto.getReason()); + } + + @Override + @Transactional + public void deleteCategory(String id, User admin) { + requireLogin(admin); + CampusCategory category = requireCategory(id); + category.setDelFlag(1); + category.setUpdateBy(admin.getId()); + categoryMapper.updateById(category); + logAdmin(admin, "category_delete", "category", category.getId(), category.getName()); + } + + @Override + public IPage pagePosts(CampusPageQuery query, User user) { + CampusPageQuery pageQuery = normalizeQuery(query); + LambdaQueryWrapper wrapper = buildPostWrapper(pageQuery); + if ("latest".equals(pageQuery.getSortType())) { + wrapper.orderByDesc(CampusPost::getCreateTime); + } else { + wrapper.orderByDesc(CampusPost::getHotScore) + .orderByDesc(CampusPost::getLikeCount) + .orderByDesc(CampusPost::getCommentCount) + .orderByDesc(CampusPost::getCreateTime); + } + IPage page = postMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), wrapper); + fillPostListFields(page.getRecords(), user); + return page; + } + + @Override + public CampusPost matchPartnerPost(CampusPageQuery query, User user) { + CampusPageQuery pageQuery = normalizeQuery(query); + LambdaQueryWrapper wrapper = new LambdaQueryWrapper() + .eq(CampusPost::getPostType, "partner") + .eq(StringUtils.isNotBlank(pageQuery.getPartnerType()), CampusPost::getPartnerType, pageQuery.getPartnerType()) + .eq(StringUtils.isNotBlank(pageQuery.getRegionId()), CampusPost::getRegionId, pageQuery.getRegionId()) + .eq(StringUtils.isNotBlank(pageQuery.getCategoryId()), CampusPost::getCategoryId, pageQuery.getCategoryId()) + .eq(pageQuery.getStatus() != null, CampusPost::getStatus, pageQuery.getStatus()) + .eq(pageQuery.getAuditStatus() != null, CampusPost::getAuditStatus, pageQuery.getAuditStatus()) + .like(StringUtils.isNotBlank(pageQuery.getKeyword()), CampusPost::getTitle, pageQuery.getKeyword()) + .like(StringUtils.isNotBlank(pageQuery.getTag()), CampusPost::getTags, pageQuery.getTag()) + .orderByDesc(CampusPost::getCreateTime) + .last("limit 1"); + CampusPost post = postMapper.selectOne(wrapper); + if (post != null) { + fillPostListFields(Collections.singletonList(post), user); + } + return post; + } + + @Override + public IPage pageMyPosts(CampusPageQuery query, User user) { + requireLogin(user); + CampusPageQuery pageQuery = normalizeQuery(query); + pageQuery.setUserId(user.getId()); + IPage page = postMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), buildPostWrapper(pageQuery) + .ne(CampusPost::getStatus, STATUS_DELETED) + .orderByDesc(CampusPost::getCreateTime)); + fillPostListFields(page.getRecords(), user); + return page; + } + + @Override + @Transactional + public CampusPostDetailVO postDetail(String id, User user) { + CampusPost post = requirePost(id); + post.setBrowseCount(safeInt(post.getBrowseCount()) + 1); + post.setHotScore(calcHotScore(post)); + postMapper.updateById(post); + CampusPostDetailVO vo = new CampusPostDetailVO(); + BeanUtils.copyProperties(post, vo); + fillPostListFields(Collections.singletonList(vo), user); + vo.setUnreadCommentCount(user == null ? 0 : unreadHashPrefixValue(commentUnreadKey(user.getId()), post.getId() + ":")); + vo.setUnreadChatCount(user == null ? 0 : unreadHashValue(chatUnreadKey(user.getId()), post.getId())); + return vo; + } + + @Override + @Transactional + public CampusPost publishPost(CampusPostDTO dto, User user) { + requireLogin(user); + ensureRealName(user, "发布校园圈内容"); + validatePublishUser(user); + validatePostDTO(dto); + String postType = StringUtils.defaultIfBlank(dto.getPostType(), "wall"); + String titleBizType = "partner".equals(postType) ? "campus_partner_title" : "campus_wall_title"; + String contentBizType = "partner".equals(postType) ? "campus_partner_content" : "campus_wall_content"; + String imageBizType = "partner".equals(postType) ? "campus_partner_image" : "campus_wall_image"; + auditStrictText(titleBizType, dto.getId(), user.getId(), dto.getTitle(), dto.getRegionId()); + auditStrictText(contentBizType, dto.getId(), user.getId(), publishContentText(dto), dto.getRegionId()); + auditStrictImages(imageBizType, user.getId(), dto.getImages(), dto.getRegionId()); + + CampusPost post = StringUtils.isNotBlank(dto.getId()) ? requireOwnPost(dto.getId(), user) : new CampusPost(); + post.setRegionId(StringUtils.defaultIfBlank(dto.getRegionId(), user.getDepartmentId())); + post.setRegionName(StringUtils.defaultIfBlank(dto.getRegionName(), user.getDepartmentTitle())); + post.setUserId(user.getId()); + post.setUserName(resolveDisplayName(dto.getUserName(), user)); + post.setUserAvatar(resolveDisplayAvatar(dto.getUserAvatar(), user)); + post.setPostType(postType); + post.setPartnerType("partner".equals(postType) ? dto.getPartnerType() : null); + post.setCategoryId(dto.getCategoryId()); + post.setCategoryName(dto.getCategoryName()); + post.setTitle(dto.getTitle().trim()); + post.setContent(dto.getContent()); + post.setContentText(publishContentText(dto)); + post.setImages(toJson(dto.getImages())); + post.setCoverImage(dto.getImages() == null || dto.getImages().isEmpty() ? null : dto.getImages().get(0)); + post.setTags(toJson(dto.getTags())); + post.setLocation(dto.getLocation()); + post.setActivityTime(dto.getActivityTime()); + post.setTargetCount(dto.getTargetCount()); + post.setContactType("chat"); + post.setContactWechat(StringUtils.trimToNull(dto.getContactWechat())); + post.setContactPhone(StringUtils.trimToNull(dto.getContactPhone())); + post.setStatus(STATUS_NORMAL); + post.setAuditStatus(AUDIT_PASS); + post.setBrowseCount(safeInt(post.getBrowseCount())); + post.setLikeCount(safeInt(post.getLikeCount())); + post.setCommentCount(safeInt(post.getCommentCount())); + post.setChatCount(safeInt(post.getChatCount())); + post.setComplaintCount(safeInt(post.getComplaintCount())); + post.setHotScore(calcHotScore(post)); + if (StringUtils.isBlank(dto.getId())) { + post.setCreateBy(user.getId()); + postMapper.insert(post); + } else { + post.setUpdateBy(user.getId()); + postMapper.updateById(post); + } + return post; + } + + @Override + public Map auditReleaseImage(String imageUrl, User user) { + requireLogin(user); + ensureRealName(user, "上传校园圈图片"); + if (StringUtils.isBlank(imageUrl)) { + throw new RuntimeException("图片地址不能为空"); + } + auditStrictImages("campus_release_image", user.getId(), Collections.singletonList(imageUrl), user.getDepartmentId()); + Map result = new HashMap<>(); + result.put("passed", true); + result.put("url", imageUrl); + return result; + } + + @Override + @Transactional + public Map toggleLike(String targetType, String targetId, String reaction, User user) { + requireLogin(user); + ensureNotMuted(user.getId()); + if (StringUtils.isAnyBlank(targetType, targetId)) { + throw new RuntimeException("点赞参数不完整"); + } + String normalizedReaction = StringUtils.defaultString(reaction); + CampusLike like = likeMapper.selectOne(new LambdaQueryWrapper() + .eq(CampusLike::getUserId, user.getId()) + .eq(CampusLike::getTargetType, targetType) + .eq(CampusLike::getTargetId, targetId) + .eq(CampusLike::getReaction, normalizedReaction) + .last("limit 1")); + boolean liked = like == null || !Integer.valueOf(STATUS_NORMAL).equals(like.getStatus()); + if (like == null) { + like = new CampusLike(); + like.setUserId(user.getId()); + like.setTargetType(targetType); + like.setTargetId(targetId); + like.setReaction(normalizedReaction); + like.setStatus(STATUS_NORMAL); + like.setCreateBy(user.getId()); + likeMapper.insert(like); + } else { + like.setStatus(liked ? STATUS_NORMAL : STATUS_OFF_SHELF); + like.setUpdateBy(user.getId()); + likeMapper.updateById(like); + } + int count = updateLikeCounter(targetType, targetId, liked ? 1 : -1); + Map map = new HashMap<>(); + map.put("liked", liked); + map.put("count", count); + if ("reaction".equals(targetType)) { + map.put("reactionCount", likeMapper.selectCount(new LambdaQueryWrapper() + .eq(CampusLike::getTargetType, "reaction") + .eq(CampusLike::getTargetId, targetId) + .eq(CampusLike::getReaction, normalizedReaction) + .eq(CampusLike::getStatus, STATUS_NORMAL))); + } + return map; + } + + @Override + @Transactional + public CampusComment addComment(CampusCommentDTO dto, User user) { + requireLogin(user); + ensureRealName(user, "评论校园圈内容"); + ensureNotMuted(user.getId()); + if (dto == null || StringUtils.isAnyBlank(dto.getPostId(), dto.getContent())) { + throw new RuntimeException("评论内容不能为空"); + } + CampusPost post = requirePost(dto.getPostId()); + auditStrictText("campus_comment", null, user.getId(), dto.getContent(), post.getRegionId()); + CampusComment comment = new CampusComment(); + comment.setPostId(dto.getPostId()); + comment.setParentId(dto.getParentId()); + comment.setUserId(user.getId()); + comment.setUserName(resolveDisplayName(dto.getUserName(), user)); + comment.setUserAvatar(resolveDisplayAvatar(dto.getUserAvatar(), user)); + comment.setReplyToUserId(dto.getReplyToUserId()); + comment.setReplyToUserName(dto.getReplyToUserName()); + comment.setContent(dto.getContent()); + comment.setLikeCount(0); + comment.setAuditStatus(AUDIT_PASS); + comment.setStatus(STATUS_NORMAL); + comment.setCreateBy(user.getId()); + commentMapper.insert(comment); + post.setCommentCount(safeInt(post.getCommentCount()) + 1); + post.setHotScore(calcHotScore(post)); + postMapper.updateById(post); + if (!user.getId().equals(post.getUserId())) { + incrementUnread(commentUnreadKey(post.getUserId()), commentUnreadField(post.getId(), comment.getId())); + } + return comment; + } + + @Override + public IPage pageComments(String postId, Integer pageNum, Integer pageSize, User user) { + requirePost(postId); + IPage page = commentMapper.selectPage(new Page<>(safePage(pageNum), safeSize(pageSize)), new LambdaQueryWrapper() + .eq(CampusComment::getPostId, postId) + .isNull(CampusComment::getParentId) + .eq(CampusComment::getStatus, STATUS_NORMAL) + .eq(CampusComment::getAuditStatus, AUDIT_PASS) + .orderByDesc(CampusComment::getCreateTime)); + fillCommentReplies(page.getRecords()); + if (user != null) { + removeUnreadByPrefix(commentUnreadKey(user.getId()), postId + ":"); + } + return page; + } + + @Override + public IPage pageMyMessageComments(CampusPageQuery query, User user) { + requireLogin(user); + CampusPageQuery pageQuery = normalizeQuery(query); + List postIds = postMapper.selectList(new LambdaQueryWrapper() + .eq(CampusPost::getUserId, user.getId()) + .ne(CampusPost::getStatus, STATUS_DELETED)) + .stream().map(CampusPost::getId).filter(StringUtils::isNotBlank).collect(Collectors.toList()); + if (postIds.isEmpty()) { + return new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()); + } + IPage page = commentMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .in(CampusComment::getPostId, postIds) + .ne(CampusComment::getUserId, user.getId()) + .eq(CampusComment::getStatus, STATUS_NORMAL) + .eq(CampusComment::getAuditStatus, AUDIT_PASS) + .like(StringUtils.isNotBlank(pageQuery.getKeyword()), CampusComment::getContent, pageQuery.getKeyword()) + .orderByDesc(CampusComment::getCreateTime)); + fillCommentDisplay(page.getRecords()); + return page; + } + + @Override + public IPage pageMyComments(CampusPageQuery query, User user) { + requireLogin(user); + CampusPageQuery pageQuery = normalizeQuery(query); + IPage page = commentMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .eq(CampusComment::getUserId, user.getId()) + .eq(CampusComment::getStatus, STATUS_NORMAL) + .eq(CampusComment::getAuditStatus, AUDIT_PASS) + .like(StringUtils.isNotBlank(pageQuery.getKeyword()), CampusComment::getContent, pageQuery.getKeyword()) + .orderByDesc(CampusComment::getCreateTime)); + fillCommentDisplay(page.getRecords()); + return page; + } + + @Override + public void deleteMyCommentMessage(String commentId, User user) { + requireLogin(user); + removeUnreadByPrefix(commentUnreadKey(user.getId()), ":" + commentId); + } + + @Override + @Transactional + public CampusChatSession createChatSession(String postId, User user) { + requireLogin(user); + ensureRealName(user, "联系校园圈搭子"); + ensureNotMuted(user.getId()); + CampusPost post = requirePost(postId); + if (user.getId().equals(post.getUserId())) { + throw new RuntimeException("不能联系自己发布的内容"); + } + CampusChatSession session = chatSessionMapper.selectOne(new LambdaQueryWrapper() + .eq(CampusChatSession::getPostId, postId) + .eq(CampusChatSession::getOwnerId, post.getUserId()) + .eq(CampusChatSession::getPeerUserId, user.getId()) + .last("limit 1")); + if (session == null) { + session = new CampusChatSession(); + session.setPostId(postId); + session.setOwnerId(post.getUserId()); + session.setPeerUserId(user.getId()); + session.setSessionId(UUID.randomUUID().toString().replace("-", "")); + session.setEnded(0); + session.setOwnerDeleted(0); + session.setPeerDeleted(0); + session.setLastMessageTime(new Date()); + session.setCreateBy(user.getId()); + chatSessionMapper.insert(session); + post.setChatCount(safeInt(post.getChatCount()) + 1); + post.setJoinedCount(safeInt(post.getJoinedCount()) + 1); + post.setHotScore(calcHotScore(post)); + postMapper.updateById(post); + } else { + session.setOwnerDeleted(0); + session.setPeerDeleted(0); + session.setUpdateBy(user.getId()); + chatSessionMapper.updateById(session); + } + fillSessionDisplay(Collections.singletonList(session), user); + return session; + } + + @Override + public CampusChatSession getChatSession(String sessionId, User user) { + CampusChatSession session = requireChatSession(sessionId, user); + fillSessionDisplay(Collections.singletonList(session), user); + return session; + } + + @Override + @Transactional + public CampusChatMessage sendChatMessage(CampusChatMessageDTO dto, User user) { + requireLogin(user); + ensureRealName(user, "发送校园圈消息"); + ensureNotMuted(user.getId()); + if (dto == null || StringUtils.isAnyBlank(dto.getSessionId(), dto.getContent())) { + throw new RuntimeException("消息内容不能为空"); + } + CampusChatSession session = requireChatSession(dto.getSessionId(), user); + String receiverId = resolveChatReceiver(session, user.getId()); + Integer messageType = dto.getMessageType() == null ? 1 : dto.getMessageType(); + CampusPost post = requirePost(session.getPostId()); + if (messageType == 1) { + auditStrictText("campus_chat_message", null, user.getId(), dto.getContent(), post.getRegionId()); + } else if (messageType == 2) { + auditStrictImages("campus_chat_message_image", user.getId(), Collections.singletonList(dto.getContent()), post.getRegionId()); + } else { + throw new RuntimeException("消息类型不支持"); + } + CampusChatMessage message = new CampusChatMessage(); + message.setSessionId(session.getSessionId()); + message.setPostId(session.getPostId()); + message.setSenderId(user.getId()); + message.setReceiverId(receiverId); + message.setMessageType(messageType); + message.setContent(dto.getContent()); + message.setAuditStatus(AUDIT_PASS); + message.setStatus(STATUS_NORMAL); + message.setReadFlag(0); + message.setClientMsgId(dto.getClientMsgId()); + message.setCreateBy(user.getId()); + chatMessageMapper.insert(message); + session.setLastMessageTime(new Date()); + session.setLastMessage(messageType == 2 ? "[图片]" : StringUtils.left(dto.getContent(), 80)); + session.setUpdateBy(user.getId()); + chatSessionMapper.updateById(session); + incrementUnread(chatUnreadKey(receiverId), session.getSessionId()); + deliverChatMessage(message); + return message; + } + + @Override + public IPage pageChatMessages(String sessionId, Integer pageNum, Integer pageSize, User user) { + CampusChatSession session = requireChatSession(sessionId, user); + IPage page = chatMessageMapper.selectPage(new Page<>(safePage(pageNum), safeSize(pageSize)), new LambdaQueryWrapper() + .eq(CampusChatMessage::getSessionId, session.getSessionId()) + .eq(CampusChatMessage::getStatus, STATUS_NORMAL) + .orderByDesc(CampusChatMessage::getCreateTime)); + chatMessageMapper.update(null, new LambdaUpdateWrapper() + .eq(CampusChatMessage::getSessionId, session.getSessionId()) + .eq(CampusChatMessage::getReceiverId, user.getId()) + .eq(CampusChatMessage::getReadFlag, 0) + .set(CampusChatMessage::getReadFlag, 1)); + removeUnreadField(chatUnreadKey(user.getId()), session.getSessionId()); + return page; + } + + @Override + public IPage pageMySessions(CampusPageQuery query, User user) { + requireLogin(user); + CampusPageQuery pageQuery = normalizeQuery(query); + IPage page = chatSessionMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .and(w -> w.eq(CampusChatSession::getOwnerId, user.getId()).eq(CampusChatSession::getOwnerDeleted, 0) + .or().eq(CampusChatSession::getPeerUserId, user.getId()).eq(CampusChatSession::getPeerDeleted, 0)) + .orderByDesc(CampusChatSession::getLastMessageTime) + .orderByDesc(CampusChatSession::getCreateTime)); + fillSessionDisplay(page.getRecords(), user); + return page; + } + + @Override + public void deleteMySessionMessage(String sessionId, User user) { + requireLogin(user); + CampusChatSession session = requireChatSession(sessionId, user); + if (user.getId().equals(session.getOwnerId())) { + session.setOwnerDeleted(1); + } else { + session.setPeerDeleted(1); + } + session.setUpdateBy(user.getId()); + chatSessionMapper.updateById(session); + removeUnreadField(chatUnreadKey(user.getId()), session.getSessionId()); + } + + @Override + @Transactional + public CampusComplaint submitComplaint(CampusComplaintDTO dto, User user) { + requireLogin(user); + ensureRealName(user, "举报校园圈内容"); + if (dto == null || StringUtils.isAnyBlank(dto.getComplaintType(), dto.getReason())) { + throw new RuntimeException("举报类型和原因不能为空"); + } + String regionId = resolveComplaintRegion(dto); + auditStrictText("campus_complaint", null, user.getId(), dto.getReason(), regionId); + auditStrictImages("campus_complaint_image", user.getId(), dto.getImages(), regionId); + CampusComplaint complaint = new CampusComplaint(); + complaint.setRegionId(regionId); + complaint.setReporterId(user.getId()); + complaint.setReporterName(defaultString(user.getNickname(), user.getUsername())); + complaint.setPostId(dto.getPostId()); + complaint.setCommentId(dto.getCommentId()); + complaint.setSessionId(dto.getSessionId()); + complaint.setComplaintType(dto.getComplaintType()); + complaint.setReason(dto.getReason()); + complaint.setImages(toJson(dto.getImages())); + complaint.setStatus(0); + complaint.setTargetUserId(resolveComplaintTargetUserId(dto)); + complaint.setCreateBy(user.getId()); + complaintMapper.insert(complaint); + if (StringUtils.isNotBlank(dto.getPostId())) { + CampusPost post = postMapper.selectById(dto.getPostId()); + if (post != null) { + post.setComplaintCount(safeInt(post.getComplaintCount()) + 1); + postMapper.updateById(post); + } + } + return complaint; + } + + @Override + public IPage pageMyComplaints(CampusPageQuery query, User user) { + requireLogin(user); + CampusPageQuery pageQuery = normalizeQuery(query); + IPage page = complaintMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .eq(CampusComplaint::getReporterId, user.getId()) + .orderByDesc(CampusComplaint::getCreateTime)); + fillComplaintDisplay(page.getRecords()); + return page; + } + + @Override + public Map unreadSummary(User user) { + requireLogin(user); + int chatUnread = sumUnreadHash(chatUnreadKey(user.getId())); + int commentUnread = sumUnreadHash(commentUnreadKey(user.getId())); + Map map = new HashMap<>(); + map.put("campusChatUnread", chatUnread); + map.put("campusCommentUnread", commentUnread); + map.put("myCircleUnread", commentUnread); + map.put("campusUnread", commentUnread); + return map; + } + + @Override + public void markAllUnreadRead(User user) { + requireLogin(user); + if (stringRedisTemplate != null) { + stringRedisTemplate.delete(chatUnreadKey(user.getId())); + stringRedisTemplate.delete(commentUnreadKey(user.getId())); + } + chatMessageMapper.update(null, new LambdaUpdateWrapper() + .eq(CampusChatMessage::getReceiverId, user.getId()) + .eq(CampusChatMessage::getReadFlag, 0) + .set(CampusChatMessage::getReadFlag, 1)); + } + + @Override + public void deleteComment(String id, User user) { + requireLogin(user); + CampusComment comment = requireComment(id); + if (!user.getId().equals(comment.getUserId())) { + throw new RuntimeException("只能删除自己的评论"); + } + softDeleteComment(comment, user.getId()); + } + + @Override + public void updateMyPostStatus(CampusAdminActionDTO dto, User user) { + requireLogin(user); + CampusPost post = requireOwnPost(dto == null ? null : dto.getPostId(), user); + int status = normalizeOwnerStatus(dto.getStatus()); + post.setStatus(status); + post.setOffShelfReason(dto.getReason()); + post.setUpdateBy(user.getId()); + postMapper.updateById(post); + } + + @Override + public void updatePostStatus(CampusAdminActionDTO dto, User admin) { + requireLogin(admin); + CampusPost post = requirePost(dto == null ? null : dto.getPostId()); + if (dto.getStatus() != null) { + post.setStatus(dto.getStatus()); + } + if (dto.getAuditStatus() != null) { + post.setAuditStatus(dto.getAuditStatus()); + } + post.setOffShelfReason(dto.getReason()); + post.setUpdateBy(admin.getId()); + postMapper.updateById(post); + logAdmin(admin, "post_status", "post", post.getId(), dto.getReason()); + } + + @Override + public IPage adminPageComments(String postId, Integer pageNum, Integer pageSize) { + IPage page = commentMapper.selectPage(new Page<>(safePage(pageNum), safeSize(pageSize)), new LambdaQueryWrapper() + .eq(CampusComment::getPostId, postId) + .orderByDesc(CampusComment::getCreateTime)); + fillCommentReplies(page.getRecords()); + return page; + } + + @Override + public void adminDeleteComment(String id, User admin) { + requireLogin(admin); + softDeleteComment(requireComment(id), admin.getId()); + logAdmin(admin, "comment_delete", "comment", id, "后台删除评论"); + } + + @Override + public IPage adminPageComplaints(CampusPageQuery query) { + CampusPageQuery pageQuery = normalizeQuery(query); + IPage page = complaintMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(pageQuery.getRegionId()), CampusComplaint::getRegionId, pageQuery.getRegionId()) + .eq(pageQuery.getStatus() != null, CampusComplaint::getStatus, pageQuery.getStatus()) + .eq(StringUtils.isNotBlank(pageQuery.getType()), CampusComplaint::getComplaintType, pageQuery.getType()) + .orderByDesc(CampusComplaint::getCreateTime)); + fillComplaintDisplay(page.getRecords()); + return page; + } + + @Override + @Transactional + public void handleComplaint(CampusAdminActionDTO dto, User admin) { + requireLogin(admin); + CampusComplaint complaint = requireComplaint(dto == null ? null : dto.getComplaintId()); + if (dto == null) { + dto = new CampusAdminActionDTO(); + } + complaint.setStatus(dto.getStatus() == null ? 1 : dto.getStatus()); + complaint.setHandleResult(dto.getHandleResult()); + complaint.setAdminId(admin.getId()); + complaint.setAdminName(defaultString(admin.getNickname(), admin.getUsername())); + complaint.setHandleTime(new Date()); + complaint.setUpdateBy(admin.getId()); + complaintMapper.updateById(complaint); + boolean complaintAccepted = Integer.valueOf(1).equals(complaint.getStatus()); + boolean shouldBlockUser = complaintAccepted || Boolean.TRUE.equals(dto.getBlockUser()); + if (shouldBlockUser) { + String targetUserId = resolveComplaintTargetUserId(complaint); + if (StringUtils.isNotBlank(targetUserId)) { + complaint.setTargetUserId(targetUserId); + complaintMapper.updateById(complaint); + saveUserLimit(dtoForLimit(dto, targetUserId, complaint.getRegionId()), admin); + } + if (complaintAccepted) { + disposeAcceptedComplaint(complaint, admin, dto.getReason()); + } + } + logAdmin(admin, "complaint_handle", "complaint", complaint.getId(), dto.getHandleResult()); + } + + @Override + public void unblockComplaintUser(CampusAdminActionDTO dto, User admin) { + requireLogin(admin); + String userId = dto == null ? null : dto.getUserId(); + if (StringUtils.isBlank(userId) && dto != null && StringUtils.isNotBlank(dto.getComplaintId())) { + userId = resolveComplaintTargetUserId(requireComplaint(dto.getComplaintId())); + } + if (StringUtils.isBlank(userId)) { + throw new RuntimeException("用户ID不能为空"); + } + CampusUserLimit limit = activeLimit(userId); + if (limit != null) { + limit.setMuted(0); + limit.setPublishBanned(0); + limit.setPermanentBanned(0); + limit.setEndTime(new Date()); + limit.setUpdateBy(admin.getId()); + userLimitMapper.updateById(limit); + logAdmin(admin, "user_unblock", "user", userId, dto.getReason()); + } + } + + @Override + public IPage adminPageUserLimits(CampusPageQuery query) { + CampusPageQuery pageQuery = normalizeQuery(query); + return userLimitMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(pageQuery.getRegionId()), CampusUserLimit::getRegionId, pageQuery.getRegionId()) + .like(StringUtils.isNotBlank(pageQuery.getKeyword()), CampusUserLimit::getUserName, pageQuery.getKeyword()) + .orderByDesc(CampusUserLimit::getCreateTime)); + } + + @Override + public CampusUserLimit saveUserLimit(CampusAdminActionDTO dto, User admin) { + requireLogin(admin); + if (dto == null || StringUtils.isBlank(dto.getUserId())) { + throw new RuntimeException("用户ID不能为空"); + } + CampusUserLimit limit = userLimitMapper.selectOne(new LambdaQueryWrapper() + .eq(CampusUserLimit::getUserId, dto.getUserId()) + .last("limit 1")); + User target = userService == null ? null : userService.findById(dto.getUserId()); + boolean insert = limit == null; + if (insert) { + limit = new CampusUserLimit(); + limit.setUserId(dto.getUserId()); + limit.setCreateBy(admin.getId()); + } + limit.setRegionId(StringUtils.defaultIfBlank(dto.getId(), admin.getDepartmentId())); + limit.setUserName(target == null ? dto.getUserId() : defaultString(target.getNickname(), target.getUsername())); + limit.setMuted(dto.getMuted() == null ? 0 : dto.getMuted()); + limit.setPublishBanned(dto.getPublishBanned() == null ? 0 : dto.getPublishBanned()); + limit.setPermanentBanned(dto.getPermanentBanned() == null ? 0 : dto.getPermanentBanned()); + limit.setBanReason(dto.getReason()); + limit.setStartTime(dto.getStartTime() == null ? new Date() : dto.getStartTime()); + limit.setEndTime(dto.getEndTime()); + limit.setUpdateBy(admin.getId()); + if (insert) { + userLimitMapper.insert(limit); + } else { + userLimitMapper.updateById(limit); + } + logAdmin(admin, "user_limit", "user", dto.getUserId(), dto.getReason()); + return limit; + } + + @Override + public IPage adminPageLogs(CampusPageQuery query) { + CampusPageQuery pageQuery = normalizeQuery(query); + return adminLogMapper.selectPage(new Page<>(pageQuery.getPageNum(), pageQuery.getPageSize()), new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(pageQuery.getRegionId()), CampusAdminLog::getRegionId, pageQuery.getRegionId()) + .orderByDesc(CampusAdminLog::getCreateTime)); + } + + @Override + public IPage adminAuditLogs(String regionId, Integer pageNumber, Integer pageSize) { + IPage page = auditLogMapper.selectPage(new Page<>(safePage(pageNumber), safeSize(pageSize)), new LambdaQueryWrapper() + .likeRight(IeContentAuditLog::getBizType, "campus_") + .eq(StringUtils.isNotBlank(regionId), IeContentAuditLog::getRegionId, regionId) + .orderByDesc(IeContentAuditLog::getCreateTime) + .orderByDesc(IeContentAuditLog::getId)); + fillAuditLogUserMobiles(page.getRecords()); + return page; + } + + private LambdaQueryWrapper buildPostWrapper(CampusPageQuery pageQuery) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(StringUtils.isNotBlank(pageQuery.getRegionId()), CampusPost::getRegionId, pageQuery.getRegionId()) + .eq(StringUtils.isNotBlank(pageQuery.getUserId()), CampusPost::getUserId, pageQuery.getUserId()) + .eq(StringUtils.isNotBlank(pageQuery.getPostType()), CampusPost::getPostType, pageQuery.getPostType()) + .eq(StringUtils.isNotBlank(pageQuery.getPartnerType()), CampusPost::getPartnerType, pageQuery.getPartnerType()) + .eq(StringUtils.isNotBlank(pageQuery.getCategoryId()), CampusPost::getCategoryId, pageQuery.getCategoryId()) + .eq(pageQuery.getAuditStatus() != null, CampusPost::getAuditStatus, pageQuery.getAuditStatus()) + .eq(pageQuery.getStatus() != null, CampusPost::getStatus, pageQuery.getStatus()) + .like(StringUtils.isNotBlank(pageQuery.getTag()), CampusPost::getTags, pageQuery.getTag()) + .and(StringUtils.isNotBlank(pageQuery.getKeyword()), w -> w + .like(CampusPost::getTitle, pageQuery.getKeyword()) + .or().like(CampusPost::getContentText, pageQuery.getKeyword()) + .or().like(CampusPost::getTags, pageQuery.getKeyword()) + .or().like(CampusPost::getLocation, pageQuery.getKeyword())); + return wrapper; + } + + private void fillPostListFields(List posts, User user) { + if (posts == null || posts.isEmpty()) return; + Set postIds = posts.stream().map(CampusPost::getId).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + Set likedIds = new HashSet<>(); + Map> reactionCountMap = batchReactionCounts(postIds); + if (user != null && !postIds.isEmpty()) { + likedIds = likeMapper.selectList(new LambdaQueryWrapper() + .eq(CampusLike::getUserId, user.getId()) + .eq(CampusLike::getTargetType, "post") + .in(CampusLike::getTargetId, postIds) + .eq(CampusLike::getStatus, STATUS_NORMAL)) + .stream().map(CampusLike::getTargetId).collect(Collectors.toSet()); + } + for (CampusPost post : posts) { + post.setImageList(parseStringList(post.getImages())); + post.setTagList(parseStringList(post.getTags())); + post.setLiked(likedIds.contains(post.getId())); + post.setMine(user != null && user.getId().equals(post.getUserId())); + post.setReactionCounts(reactionCountMap.getOrDefault(post.getId(), Collections.emptyMap())); + post.setUnreadCommentCount(user == null ? 0 : unreadHashPrefixValue(commentUnreadKey(user.getId()), post.getId() + ":")); + post.setUnreadChatCount(user == null ? 0 : unreadHashValue(chatUnreadKey(user.getId()), post.getId())); + } + } + + private Map> batchReactionCounts(Set postIds) { + if (postIds == null || postIds.isEmpty()) return Collections.emptyMap(); + List> rows = likeMapper.selectMaps(new QueryWrapper() + .select("target_id AS targetId", "reaction", "COUNT(1) AS count") + .eq("target_type", "reaction") + .eq("status", STATUS_NORMAL) + .in("target_id", postIds) + .groupBy("target_id", "reaction")); + Map> result = new HashMap<>(); + for (Map row : rows) { + Object targetIdValue = row.containsKey("targetId") ? row.get("targetId") : row.get("target_id"); + Object countValue = row.containsKey("count") ? row.get("count") : row.get("COUNT(1)"); + String targetId = Objects.toString(targetIdValue, ""); + String reaction = Objects.toString(row.get("reaction"), ""); + if (StringUtils.isAnyBlank(targetId, reaction)) continue; + Number count = countValue instanceof Number ? (Number) countValue : 0; + result.computeIfAbsent(targetId, k -> new HashMap<>()).put(reaction, count.intValue()); + } + return result; + } + + private void fillCommentReplies(List parents) { + if (parents == null || parents.isEmpty()) return; + List parentIds = parents.stream().map(CampusComment::getId).collect(Collectors.toList()); + Map> replies = commentMapper.selectList(new LambdaQueryWrapper() + .in(CampusComment::getParentId, parentIds) + .eq(CampusComment::getStatus, STATUS_NORMAL) + .eq(CampusComment::getAuditStatus, AUDIT_PASS) + .orderByAsc(CampusComment::getCreateTime)) + .stream().collect(Collectors.groupingBy(CampusComment::getParentId)); + parents.forEach(parent -> parent.setReplies(replies.getOrDefault(parent.getId(), Collections.emptyList()))); + } + + private void fillCommentDisplay(List comments) { + if (comments == null || comments.isEmpty()) return; + Set postIds = comments.stream().map(CampusComment::getPostId).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + Map posts = postIds.isEmpty() ? Collections.emptyMap() : postMapper.selectBatchIds(postIds) + .stream().collect(Collectors.toMap(CampusPost::getId, Function.identity(), (a, b) -> a)); + for (CampusComment comment : comments) { + CampusPost post = posts.get(comment.getPostId()); + if (post != null) { + comment.setDisplayTitle(post.getTitle()); + comment.setDisplayCover(post.getCoverImage()); + } + } + } + + private void fillSessionDisplay(List sessions, User user) { + if (sessions == null || sessions.isEmpty()) return; + Set postIds = sessions.stream().map(CampusChatSession::getPostId).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + Map posts = postIds.isEmpty() ? Collections.emptyMap() : postMapper.selectBatchIds(postIds) + .stream().collect(Collectors.toMap(CampusPost::getId, Function.identity(), (a, b) -> a)); + for (CampusChatSession session : sessions) { + CampusPost post = posts.get(session.getPostId()); + if (post != null) { + session.setDisplayTitle(post.getTitle()); + session.setDisplayCover(post.getCoverImage()); + session.setPostType(post.getPostType()); + session.setPartnerType(post.getPartnerType()); + } + session.setUnreadCount(user == null ? 0 : unreadHashValue(chatUnreadKey(user.getId()), session.getSessionId())); + } + } + + private void fillComplaintDisplay(List complaints) { + if (complaints == null || complaints.isEmpty()) return; + Set postIds = complaints.stream().map(CampusComplaint::getPostId).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + Set commentIds = complaints.stream().map(CampusComplaint::getCommentId).filter(StringUtils::isNotBlank).collect(Collectors.toSet()); + Map posts = postIds.isEmpty() ? Collections.emptyMap() : postMapper.selectBatchIds(postIds) + .stream().collect(Collectors.toMap(CampusPost::getId, Function.identity(), (a, b) -> a)); + Map comments = commentIds.isEmpty() ? Collections.emptyMap() : commentMapper.selectBatchIds(commentIds) + .stream().collect(Collectors.toMap(CampusComment::getId, Function.identity(), (a, b) -> a)); + Set targetUserIds = new HashSet<>(); + for (CampusComplaint complaint : complaints) { + String targetUserId = complaint.getTargetUserId(); + CampusComment comment = comments.get(complaint.getCommentId()); + CampusPost post = posts.get(complaint.getPostId()); + if (StringUtils.isBlank(targetUserId) && comment != null) { + targetUserId = comment.getUserId(); + } + if (StringUtils.isBlank(targetUserId) && post != null) { + targetUserId = post.getUserId(); + } + if (StringUtils.isNotBlank(targetUserId)) { + complaint.setTargetUserId(targetUserId); + targetUserIds.add(targetUserId); + } + } + Map mobileMap = userMobileMap(targetUserIds); + for (CampusComplaint complaint : complaints) { + CampusPost post = posts.get(complaint.getPostId()); + if (post != null) { + complaint.setDisplayTitle(post.getTitle()); + complaint.setDisplayCover(post.getCoverImage()); + } + CampusComment comment = comments.get(complaint.getCommentId()); + if (comment != null) { + complaint.setCommentContent(comment.getContent()); + } + complaint.setTargetUserMobile(mobileMap.get(complaint.getTargetUserId())); + } + } + + private void fillAuditLogUserMobiles(List logs) { + if (logs == null || logs.isEmpty()) return; + Set userIds = logs.stream() + .map(IeContentAuditLog::getUserId) + .filter(Objects::nonNull) + .map(String::valueOf) + .collect(Collectors.toSet()); + Map mobileMap = userMobileMap(userIds); + for (IeContentAuditLog log : logs) { + if (log.getUserId() != null) { + log.setUserMobile(mobileMap.get(String.valueOf(log.getUserId()))); + } + } + } + + private Map userMobileMap(Set userIds) { + if (userIds == null || userIds.isEmpty()) return Collections.emptyMap(); + Map result = new HashMap<>(); + for (User user : userService.getRepository().findAllById(userIds)) { + if (user != null && StringUtils.isNotBlank(user.getId())) { + result.put(user.getId(), user.getMobile()); + } + } + return result; + } + + private void validatePostDTO(CampusPostDTO dto) { + if (dto == null) throw new RuntimeException("发布内容不能为空"); + if (StringUtils.isBlank(dto.getTitle()) || dto.getTitle().trim().length() > 30) throw new RuntimeException("标题不能为空且不能超过30字"); + String text = publishContentText(dto); + if (StringUtils.isNotBlank(text) && text.length() > 1000) throw new RuntimeException("正文不能超过1000字"); + if (StringUtils.isBlank(dto.getPostType())) dto.setPostType("wall"); + if (!"wall".equals(dto.getPostType()) && !"partner".equals(dto.getPostType())) throw new RuntimeException("发布类型不正确"); + if (StringUtils.isBlank(dto.getCategoryId())) throw new RuntimeException("请选择二级分类"); + if ("partner".equals(dto.getPostType()) && StringUtils.isBlank(dto.getPartnerType())) throw new RuntimeException("请选择搭子类型"); + if (dto.getImages() != null && dto.getImages().size() > 9) throw new RuntimeException("图片最多上传9张"); + if ("partner".equals(dto.getPostType()) && dto.getTargetCount() != null && dto.getTargetCount() < 1) throw new RuntimeException("搭子人数不正确"); + if (StringUtils.isNotBlank(dto.getLocation()) && dto.getLocation().trim().length() > 30) throw new RuntimeException("地点不能超过30字"); + if (StringUtils.isNotBlank(dto.getActivityTime()) && dto.getActivityTime().trim().length() > 20) throw new RuntimeException("时间不能超过20字"); + if (StringUtils.isNotBlank(dto.getContactWechat()) && !dto.getContactWechat().trim().matches("^[a-zA-Z][-_a-zA-Z0-9]{5,19}$")) throw new RuntimeException("微信号格式不正确"); + if (StringUtils.isNotBlank(dto.getContactPhone()) && !dto.getContactPhone().trim().matches("^1[3-9]\\d{9}$")) throw new RuntimeException("手机号格式不正确"); + } + + private void validatePublishUser(User user) { + CampusUserLimit limit = activeLimit(user.getId()); + if (limit != null && (Integer.valueOf(1).equals(limit.getPublishBanned()) || Integer.valueOf(1).equals(limit.getPermanentBanned()))) { + throw new RuntimeException(StringUtils.defaultIfBlank(limit.getBanReason(), "你已被限制发布校园圈内容")); + } + } + + private void ensureNotMuted(String userId) { + CampusUserLimit limit = activeLimit(userId); + if (limit != null && (Integer.valueOf(1).equals(limit.getMuted()) || Integer.valueOf(1).equals(limit.getPermanentBanned()))) { + throw new RuntimeException(StringUtils.defaultIfBlank(limit.getBanReason(), "你已被限制在校园圈互动")); + } + } + + private CampusUserLimit activeLimit(String userId) { + if (StringUtils.isBlank(userId)) return null; + CampusUserLimit limit = userLimitMapper.selectOne(new LambdaQueryWrapper() + .eq(CampusUserLimit::getUserId, userId) + .last("limit 1")); + if (limit == null) return null; + if (Integer.valueOf(1).equals(limit.getPermanentBanned())) return limit; + return limit.getEndTime() != null && limit.getEndTime().before(new Date()) ? null : limit; + } + + private void auditStrictText(String bizType, String bizId, String userId, String content, String regionId) { + if (StringUtils.isBlank(content)) return; + IeAuditResult result = securityAuditService.audit(bizType, parseLong(bizId), parseLong(userId), content, regionId); + if (result != null && (Boolean.TRUE.equals(result.getBlocked()) || isMediumOrHigh(result.getRiskLevel()))) { + recordAuditBlock(bizType, bizId, userId, content, result); + throw new RuntimeException(TEXT_AUDIT_REJECT_MESSAGE); + } + IeAliyunModerationResult aliyun = result == null ? null : result.getAliyunResult(); + if (aliyun != null && (aliyun.isBlock() || isMediumOrHigh(aliyun.getRiskLevel()))) { + recordAliyunBlock(bizType, bizId, userId, content, aliyun); + throw new RuntimeException(TEXT_AUDIT_REJECT_MESSAGE); + } + } + + private void auditStrictImages(String bizType, String userId, List images, String regionId) { + if (images == null || images.isEmpty() || aliyunModerationService == null) return; + for (String image : images) { + if (StringUtils.isBlank(image)) continue; + IeAliyunModerationResult result = aliyunModerationService.checkImage(image, regionId, bizType, null, parseLong(userId)); + if (result != null && (result.isBlock() || isMediumOrHigh(result.getRiskLevel()))) { + recordAliyunBlock(bizType, null, userId, image, result); + throw new RuntimeException(IMAGE_AUDIT_REJECT_MESSAGE); + } + } + } + + private void recordAuditBlock(String bizType, String bizId, String userId, String content, IeAuditResult result) { + if (contentAuditLogService == null || result == null) return; + recordAuditLogRequiresNew(() -> contentAuditLogService.recordLog(bizType, parseLong(bizId), parseLong(userId), content, result.getFilteredContent(), + result.getAuditStatus() == null ? IeConstants.AUDIT_BLOCK : result.getAuditStatus(), + result.getRiskLevel() == null ? IeConstants.RISK_HIGH : result.getRiskLevel(), + result.getHitWords() == null ? "" : String.join(",", result.getHitWords()), + result.getHitCategories() == null ? "" : String.join(",", result.getHitCategories()))); + } + + private void recordAliyunBlock(String bizType, String bizId, String userId, String content, IeAliyunModerationResult result) { + if (contentAuditLogService == null || result == null) return; + String riskLevel = StringUtils.defaultIfBlank(result.getRiskLevel(), "aliyun"); + recordAuditLogRequiresNew(() -> contentAuditLogService.recordLog(bizType, parseLong(bizId), parseLong(userId), content, content, + IeConstants.AUDIT_BLOCK, aliyunRiskLevel(result.getRiskLevel()), "aliyun:" + riskLevel, result.getDetail())); + } + + private void recordAuditLogRequiresNew(Runnable recorder) { + try { + TransactionTemplate template = new TransactionTemplate(transactionManager); + template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); + template.executeWithoutResult(status -> recorder.run()); + } catch (Exception e) { + log.warn("校园圈审核日志写入失败,不影响主流程", e); + } + } + + private void softDeleteComment(CampusComment comment, String operatorId) { + comment.setStatus(STATUS_OFF_SHELF); + comment.setUpdateBy(operatorId); + commentMapper.updateById(comment); + CampusPost post = postMapper.selectById(comment.getPostId()); + if (post != null) { + post.setCommentCount(Math.max(0, safeInt(post.getCommentCount()) - 1)); + post.setHotScore(calcHotScore(post)); + postMapper.updateById(post); + } + } + + private int updateLikeCounter(String targetType, String targetId, int step) { + if ("comment".equals(targetType)) { + CampusComment comment = requireComment(targetId); + comment.setLikeCount(Math.max(0, safeInt(comment.getLikeCount()) + step)); + commentMapper.updateById(comment); + return comment.getLikeCount(); + } + CampusPost post = requirePost(targetId); + post.setLikeCount(Math.max(0, safeInt(post.getLikeCount()) + step)); + post.setHotScore(calcHotScore(post)); + postMapper.updateById(post); + return post.getLikeCount(); + } + + private CampusPost requirePost(String id) { + if (StringUtils.isBlank(id)) throw new RuntimeException("帖子ID不能为空"); + CampusPost post = postMapper.selectById(id); + if (post == null || Integer.valueOf(STATUS_DELETED).equals(post.getStatus())) throw new RuntimeException("帖子不存在"); + return post; + } + + private CampusPost requireOwnPost(String id, User user) { + CampusPost post = requirePost(id); + if (!user.getId().equals(post.getUserId())) throw new RuntimeException("只能操作自己的内容"); + return post; + } + + private CampusComment requireComment(String id) { + if (StringUtils.isBlank(id)) throw new RuntimeException("评论ID不能为空"); + CampusComment comment = commentMapper.selectById(id); + if (comment == null) throw new RuntimeException("评论不存在"); + return comment; + } + + private CampusCategory requireCategory(String id) { + if (StringUtils.isBlank(id)) throw new RuntimeException("分类ID不能为空"); + CampusCategory category = categoryMapper.selectById(id); + if (category == null) throw new RuntimeException("分类不存在"); + return category; + } + + private CampusComplaint requireComplaint(String id) { + if (StringUtils.isBlank(id)) throw new RuntimeException("举报ID不能为空"); + CampusComplaint complaint = complaintMapper.selectById(id); + if (complaint == null) throw new RuntimeException("举报不存在"); + return complaint; + } + + private CampusChatSession requireChatSession(String sessionId, User user) { + if (StringUtils.isBlank(sessionId)) throw new RuntimeException("会话ID不能为空"); + CampusChatSession session = chatSessionMapper.selectOne(new LambdaQueryWrapper() + .and(w -> w.eq(CampusChatSession::getId, sessionId).or().eq(CampusChatSession::getSessionId, sessionId)) + .last("limit 1")); + if (session == null || Integer.valueOf(1).equals(session.getEnded())) throw new RuntimeException("会话不存在或已结束"); + if (!user.getId().equals(session.getOwnerId()) && !user.getId().equals(session.getPeerUserId())) throw new RuntimeException("无权访问该会话"); + return session; + } + + private String resolveChatReceiver(CampusChatSession session, String senderId) { + return senderId.equals(session.getOwnerId()) ? session.getPeerUserId() : session.getOwnerId(); + } + + private String resolveComplaintRegion(CampusComplaintDTO dto) { + if (StringUtils.isNotBlank(dto.getPostId())) { + CampusPost post = postMapper.selectById(dto.getPostId()); + return post == null ? null : post.getRegionId(); + } + return null; + } + + private String resolveComplaintTargetUserId(CampusComplaintDTO dto) { + if (StringUtils.isNotBlank(dto.getCommentId())) { + CampusComment comment = commentMapper.selectById(dto.getCommentId()); + if (comment != null) return comment.getUserId(); + } + if (StringUtils.isNotBlank(dto.getPostId())) { + CampusPost post = postMapper.selectById(dto.getPostId()); + if (post != null) return post.getUserId(); + } + if (StringUtils.isNotBlank(dto.getSessionId())) { + CampusChatSession session = chatSessionMapper.selectOne(new LambdaQueryWrapper() + .eq(CampusChatSession::getSessionId, dto.getSessionId()).last("limit 1")); + return session == null ? null : session.getOwnerId(); + } + return null; + } + + private String resolveComplaintTargetUserId(CampusComplaint complaint) { + if (complaint == null) return null; + if (StringUtils.isNotBlank(complaint.getCommentId())) { + CampusComment comment = commentMapper.selectById(complaint.getCommentId()); + if (comment != null) return comment.getUserId(); + } + if (StringUtils.isNotBlank(complaint.getPostId())) { + CampusPost post = postMapper.selectById(complaint.getPostId()); + if (post != null) return post.getUserId(); + } + if (StringUtils.isNotBlank(complaint.getTargetUserId())) { + return complaint.getTargetUserId(); + } + if (StringUtils.isNotBlank(complaint.getSessionId())) { + CampusChatSession session = chatSessionMapper.selectOne(new LambdaQueryWrapper() + .eq(CampusChatSession::getSessionId, complaint.getSessionId()).last("limit 1")); + return session == null ? null : session.getOwnerId(); + } + return null; + } + + private void disposeAcceptedComplaint(CampusComplaint complaint, User admin, String reason) { + if (complaint == null) return; + String actionReason = StringUtils.defaultIfBlank(reason, "举报属实,后台处理"); + if (isCommentComplaint(complaint)) { + if (StringUtils.isBlank(complaint.getCommentId())) return; + CampusComment comment = commentMapper.selectById(complaint.getCommentId()); + if (comment == null || Integer.valueOf(STATUS_OFF_SHELF).equals(comment.getStatus())) return; + softDeleteComment(comment, admin.getId()); + logAdmin(admin, "complaint_comment_delete", "comment", comment.getId(), actionReason); + return; + } + if (StringUtils.isBlank(complaint.getPostId())) return; + CampusPost post = postMapper.selectById(complaint.getPostId()); + if (post == null || Integer.valueOf(STATUS_DELETED).equals(post.getStatus())) return; + post.setStatus(STATUS_OFF_SHELF); + post.setAuditStatus(2); + post.setOffShelfReason(actionReason); + post.setUpdateBy(admin.getId()); + postMapper.updateById(post); + logAdmin(admin, "complaint_post_off_shelf", "post", post.getId(), actionReason); + } + + private boolean isCommentComplaint(CampusComplaint complaint) { + return complaint != null && ("comment".equals(complaint.getComplaintType()) || StringUtils.isNotBlank(complaint.getCommentId())); + } + + private CampusAdminActionDTO dtoForLimit(CampusAdminActionDTO source, String userId, String regionId) { + CampusAdminActionDTO dto = new CampusAdminActionDTO(); + dto.setId(regionId); + dto.setUserId(userId); + dto.setMuted(source.getMuted() == null ? 1 : source.getMuted()); + dto.setPublishBanned(source.getPublishBanned() == null ? 1 : source.getPublishBanned()); + dto.setPermanentBanned(source.getPermanentBanned() == null ? 0 : source.getPermanentBanned()); + dto.setStartTime(source.getStartTime()); + dto.setEndTime(source.getEndTime()); + dto.setReason(source.getReason()); + return dto; + } + + private void deliverChatMessage(CampusChatMessage message) { + if (messagingTemplate == null || message == null) return; + messagingTemplate.convertAndSendToUser(message.getReceiverId(), "/queue/campus/message", message); + messagingTemplate.convertAndSend("/topic/campus/session/" + message.getSessionId(), message); + } + + private void incrementUnread(String key, String field) { + if (stringRedisTemplate == null || StringUtils.isAnyBlank(key, field)) return; + stringRedisTemplate.opsForHash().increment(key, field, 1L); + stringRedisTemplate.expire(key, 30, TimeUnit.DAYS); + } + + private int sumUnreadHash(String key) { + if (stringRedisTemplate == null || StringUtils.isBlank(key)) return 0; + return stringRedisTemplate.opsForHash().values(key).stream().mapToInt(v -> { + try { + return Integer.parseInt(String.valueOf(v)); + } catch (Exception e) { + return 0; + } + }).sum(); + } + + private int unreadHashValue(String key, String field) { + if (stringRedisTemplate == null || StringUtils.isAnyBlank(key, field)) return 0; + Object value = stringRedisTemplate.opsForHash().get(key, field); + if (value == null) return 0; + try { + return Integer.parseInt(String.valueOf(value)); + } catch (Exception e) { + return 0; + } + } + + private int unreadHashPrefixValue(String key, String prefix) { + if (stringRedisTemplate == null || StringUtils.isAnyBlank(key, prefix)) return 0; + Set fields = stringRedisTemplate.opsForHash().keys(key); + int total = 0; + for (Object field : fields) { + if (!String.valueOf(field).startsWith(prefix)) continue; + Object value = stringRedisTemplate.opsForHash().get(key, field); + try { + total += Integer.parseInt(String.valueOf(value)); + } catch (Exception ignored) { + } + } + return total; + } + + private void removeUnreadField(String key, String field) { + if (stringRedisTemplate != null && StringUtils.isNoneBlank(key, field)) { + stringRedisTemplate.opsForHash().delete(key, field); + } + } + + private void removeUnreadByPrefix(String key, String prefix) { + if (stringRedisTemplate == null || StringUtils.isAnyBlank(key, prefix)) return; + Set fields = stringRedisTemplate.opsForHash().keys(key); + for (Object field : fields) { + if (String.valueOf(field).startsWith(prefix) || String.valueOf(field).endsWith(prefix)) { + stringRedisTemplate.opsForHash().delete(key, field); + } + } + } + + private String chatUnreadKey(String userId) { + return StringUtils.isBlank(userId) ? "" : CHAT_UNREAD_KEY_PREFIX + userId; + } + + private String commentUnreadKey(String userId) { + return StringUtils.isBlank(userId) ? "" : COMMENT_UNREAD_KEY_PREFIX + userId; + } + + private String commentUnreadField(String postId, String commentId) { + return postId + ":" + commentId; + } + + private CampusPageQuery normalizeQuery(CampusPageQuery query) { + CampusPageQuery q = query == null ? new CampusPageQuery() : query; + q.setPageNum(q.getPageNum() == null || q.getPageNum() < 1 ? 1 : q.getPageNum()); + q.setPageSize(q.getPageSize() == null || q.getPageSize() < 1 ? 10 : Math.min(q.getPageSize(), 50)); + return q; + } + + private long safePage(Integer pageNum) { + return pageNum == null || pageNum < 1 ? 1L : pageNum; + } + + private long safeSize(Integer pageSize) { + if (pageSize == null || pageSize < 1) return 10L; + return Math.min(pageSize, 50); + } + + private int normalizeOwnerStatus(Integer status) { + if (status == null || (status != STATUS_NORMAL && status != STATUS_OFF_SHELF && status != STATUS_DELETED)) { + throw new RuntimeException("状态参数错误"); + } + return status; + } + + private int calcHotScore(CampusPost post) { + return safeInt(post.getLikeCount()) * 3 + safeInt(post.getCommentCount()) * 5 + safeInt(post.getChatCount()) * 2 + safeInt(post.getBrowseCount()); + } + + private String publishContentText(CampusPostDTO dto) { + String contentText = dto == null ? "" : StringUtils.defaultString(dto.getContentText()); + if (StringUtils.isBlank(contentText) && dto != null) { + contentText = StringUtils.defaultString(dto.getContent()).replaceAll("<[^>]+>", ""); + } + return contentText.replaceAll("\\s+", "").trim(); + } + + private List parseStringList(String json) { + if (StringUtils.isBlank(json)) return new ArrayList<>(); + try { + List list = JSON.parseArray(json, String.class); + return list == null ? new ArrayList<>() : list; + } catch (Exception e) { + return new ArrayList<>(); + } + } + + private String toJson(Object value) { + return value == null ? null : JSON.toJSONString(value); + } + + private int safeInt(Integer value) { + return value == null ? 0 : value; + } + + private String defaultString(String first, String second) { + return StringUtils.isNotBlank(first) ? first : StringUtils.defaultString(second); + } + + private String resolveDisplayName(String cachedName, User user) { + String name = defaultString(cachedName, user == null ? null : defaultString(user.getNickname(), user.getUsername())); + String value = StringUtils.defaultIfBlank(name, "半径同学").trim(); + return value.length() > 20 ? value.substring(0, 20) : value; + } + + private String resolveDisplayAvatar(String cachedAvatar, User user) { + return StringUtils.defaultIfBlank(cachedAvatar, user == null ? "" : StringUtils.defaultString(user.getAvatar())); + } + + private Long parseLong(String value) { + if (StringUtils.isBlank(value)) return null; + try { + return Long.parseLong(value); + } catch (Exception e) { + return null; + } + } + + private int aliyunRiskLevel(String riskLevel) { + if ("medium".equalsIgnoreCase(riskLevel)) return IeConstants.RISK_MEDIUM; + if ("high".equalsIgnoreCase(riskLevel)) return IeConstants.RISK_HIGH; + return IeConstants.RISK_HIGH; + } + + private boolean isMediumOrHigh(Integer riskLevel) { + return riskLevel != null && riskLevel >= IeConstants.RISK_MEDIUM; + } + + private boolean isMediumOrHigh(String riskLevel) { + return "medium".equalsIgnoreCase(riskLevel) || "high".equalsIgnoreCase(riskLevel); + } + + private void ensureRealName(User user, String actionText) { + if (userService != null && !userService.isRealNameVerified(user.getId())) { + throw new RuntimeException(StringUtils.defaultIfBlank(actionText, "操作") + "前请先完成实名认证"); + } + } + + private void requireLogin(User user) { + if (user == null || StringUtils.isBlank(user.getId())) throw new RuntimeException("请先登录"); + } + + private void logAdmin(User admin, String actionType, String targetType, String targetId, String reason) { + CampusAdminLog log = new CampusAdminLog(); + log.setRegionId(admin == null ? null : admin.getDepartmentId()); + log.setAdminId(admin == null ? null : admin.getId()); + log.setAdminName(admin == null ? null : defaultString(admin.getNickname(), admin.getUsername())); + log.setActionType(actionType); + log.setTargetType(targetType); + log.setTargetId(targetId); + log.setReason(reason); + adminLogMapper.insert(log); + } +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/vo/CampusPostDetailVO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/vo/CampusPostDetailVO.java new file mode 100644 index 00000000..8c01c2c0 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/campus/vo/CampusPostDetailVO.java @@ -0,0 +1,12 @@ +package cc.hiver.mall.campus.vo; + +import cc.hiver.mall.campus.entity.CampusPost; +import lombok.Data; +import lombok.EqualsAndHashCode; + +@Data +@EqualsAndHashCode(callSuper = true) +public class CampusPostDetailVO extends CampusPost { + private Boolean liked; + private Boolean mine; +} diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java index 9da8d939..90a9998e 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/AdminDataStatisticsController.java @@ -6,8 +6,6 @@ import cc.hiver.mall.billing.entity.BillingUsageRecord; import cc.hiver.mall.billing.mapper.BillingUsageRecordMapper; import cc.hiver.mall.dao.mapper.*; import cc.hiver.mall.entity.*; -import cc.hiver.mall.planet.entity.PlanetPool; -import cc.hiver.mall.planet.mapper.PlanetPoolMapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import io.swagger.annotations.Api; import io.swagger.annotations.ApiModelProperty; @@ -26,6 +24,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.HashSet; import java.util.List; @@ -42,9 +41,9 @@ public class AdminDataStatisticsController { private static final BigDecimal CONTENT_AUDIT_PACKAGE_PRICE = new BigDecimal("0.05"); private static final BigDecimal REAL_NAME_UNIT_PRICE = BigDecimal.ONE; - private static final String REWARD_FREE_ORDER = "免单奖励"; private static final String REWARD_COLLEGE_RANK = "学院排位赛排名奖励"; private static final String REWARD_DELIVERY_RANK = "配送排名奖励"; + private static final String REWARD_DRAW_POOL = "白嫖中奖"; @Autowired private MallOrderMapper mallOrderMapper; @@ -57,9 +56,9 @@ public class AdminDataStatisticsController { @Autowired private DealingsRecordMapper dealingsRecordMapper; @Autowired - private PlanetPoolMapper planetPoolMapper; - @Autowired private BillingUsageRecordMapper billingUsageRecordMapper; + @Autowired + private MallSettlementRecordMapper mallSettlementRecordMapper; @Data public static class DataStatisticsQuery { @@ -85,6 +84,8 @@ public class AdminDataStatisticsController { private BigDecimal couponDiscountAmount; private BigDecimal customerAverageAmount; private BigDecimal actualIncomeAmount; + private Boolean actualIncomeAvailable; + private BigDecimal settledAmount; private BigDecimal headquartersDeductionAmount; private BigDecimal deliveryFeeAmount; private Long smsCount; @@ -120,6 +121,7 @@ public class AdminDataStatisticsController { List deliveries = mallDeliveryOrderMapper.selectList(new LambdaQueryWrapper() .eq(StringUtils.isNotBlank(regionId), MallDeliveryOrder::getRegionId, regionId) + .eq(MallDeliveryOrder::getStatus, 3) .ge(MallDeliveryOrder::getFinishTime, range.start) .le(MallDeliveryOrder::getFinishTime, range.end) .isNotNull(MallDeliveryOrder::getFinishTime)); @@ -134,12 +136,7 @@ public class AdminDataStatisticsController { .eq(StringUtils.isNotBlank(regionId), DealingsRecord::getRegionId, regionId) .ge(DealingsRecord::getDealingsTime, range.start) .le(DealingsRecord::getDealingsTime, range.end) - .in(DealingsRecord::getDealingsWay, REWARD_FREE_ORDER, REWARD_COLLEGE_RANK, REWARD_DELIVERY_RANK)); - - List pools = planetPoolMapper.selectList(new LambdaQueryWrapper() - .eq(StringUtils.isNotBlank(regionId), PlanetPool::getRegionId, regionId) - .ge(PlanetPool::getDrawTime, range.start) - .le(PlanetPool::getDrawTime, range.end)); + .in(DealingsRecord::getDealingsWay, REWARD_COLLEGE_RANK, REWARD_DELIVERY_RANK, REWARD_DRAW_POOL)); List billingRecords = billingUsageRecordMapper.selectList(new LambdaQueryWrapper() .eq(StringUtils.isNotBlank(regionId), BillingUsageRecord::getRegionId, regionId) @@ -150,13 +147,36 @@ public class AdminDataStatisticsController { vo.setStartDate(range.startDate.toString()); vo.setEndDate(range.endDate.toString()); + Set refundedOrderIds = new HashSet<>(); + Set deliveryRefundOrderIds = new HashSet<>(); + for (MallRefundRecord refund : refunds) { + if (StringUtils.isBlank(refund.getOrderId())) { + continue; + } + if (refund.getStatus() != null && refund.getStatus() == 1) { + refundedOrderIds.add(refund.getOrderId()); + } + if (refund.getRefundTypeStatus() != null && (refund.getRefundTypeStatus() == 2 || refund.getRefundTypeStatus() == 3 || refund.getRefundTypeStatus() == 4)) { + deliveryRefundOrderIds.add(refund.getOrderId()); + } + } + BigDecimal goodsAndPackage = BigDecimal.ZERO; - BigDecimal deliveryFee = BigDecimal.ZERO; + BigDecimal grossDeliveryFee = BigDecimal.ZERO; + BigDecimal settlementDeliveryFee = BigDecimal.ZERO; + BigDecimal koiDiscount = BigDecimal.ZERO; Set shopIds = new HashSet<>(); Set userIds = new HashSet<>(); for (MallOrder order : orders) { goodsAndPackage = goodsAndPackage.add(amount(order.getGoodsAmount())).add(amount(order.getPackageFee())); - deliveryFee = deliveryFee.add(amount(order.getDeliveryFee())); + BigDecimal orderDeliveryFee = amount(order.getDeliveryFee()); + grossDeliveryFee = grossDeliveryFee.add(orderDeliveryFee); + if (!deliveryRefundOrderIds.contains(order.getId())) { + settlementDeliveryFee = settlementDeliveryFee.add(orderDeliveryFee); + } + if (!isRefundedOrder(order, refundedOrderIds)) { + koiDiscount = koiDiscount.add(amount(order.getFreeAmount())); + } if (StringUtils.isNotBlank(order.getShopId())) { shopIds.add(order.getShopId()); } @@ -165,25 +185,20 @@ public class AdminDataStatisticsController { } } - BigDecimal deliveryFeeAmount = subtractWechatFee(deliveryFee); - BigDecimal customerAverageBase = subtractWechatFee(goodsAndPackage); - BigDecimal grossFlow = subtractWechatFee(goodsAndPackage.add(deliveryFee)); + BigDecimal deliveryFeeAmount = subtractWechatFee(settlementDeliveryFee); + BigDecimal grossFlow = subtractWechatFee(goodsAndPackage.add(grossDeliveryFee)); BigDecimal refundAmount = sumRefundAmount(refunds, 1); BigDecimal afterSaleAmount = sumRefundAmount(refunds, 4); - BigDecimal effectiveFlow = grossFlow.subtract(refundAmount).subtract(afterSaleAmount).subtract(deliveryFeeAmount); + BigDecimal effectiveFlow = subtractWechatFee(goodsAndPackage); BigDecimal headquartersDeduction = effectiveFlow.multiply(ONE_PERCENT); - BigDecimal koiDiscount = sumDealingsAmount(rewardRecords, REWARD_FREE_ORDER); BigDecimal collegeRankReward = sumDealingsAmount(rewardRecords, REWARD_COLLEGE_RANK); BigDecimal deliveryRankReward = sumDealingsAmount(rewardRecords, REWARD_DELIVERY_RANK); BigDecimal couponDiscount = coupons.stream() .map(MallUserCoupon::getDiscountAmount) .map(this::amount) .reduce(BigDecimal.ZERO, BigDecimal::add); - BigDecimal drawPoolAmount = pools.stream() - .map(PlanetPool::getPoolAmount) - .map(this::amount) - .reduce(BigDecimal.ZERO, BigDecimal::add); + BigDecimal drawPoolAmount = sumDealingsAmount(rewardRecords, REWARD_DRAW_POOL); long smsCount = billingRecords.stream().filter(item -> "sms".equals(item.getRecordType())).count(); long contentAuditCount = billingRecords.stream().filter(item -> "content_audit".equals(item.getRecordType())).count(); @@ -191,15 +206,21 @@ public class AdminDataStatisticsController { BigDecimal smsCost = BigDecimal.valueOf(smsCount).multiply(SMS_UNIT_PRICE); BigDecimal contentAuditCost = BigDecimal.valueOf((contentAuditCount + 99) / 100).multiply(CONTENT_AUDIT_PACKAGE_PRICE); BigDecimal realNameCost = BigDecimal.valueOf(realNameAuthCount).multiply(REAL_NAME_UNIT_PRICE); - BigDecimal actualIncome = effectiveFlow + BigDecimal rewardTotalAmount = collegeRankReward.add(drawPoolAmount).add(deliveryRankReward); + boolean actualIncomeAvailable = isActualIncomeAvailable(range); + BigDecimal settledAmount = actualIncomeAvailable ? sumSettledAmount(regionId, range) : BigDecimal.ZERO; + BigDecimal actualIncome = grossFlow + .subtract(deliveryFeeAmount) .subtract(headquartersDeduction) .subtract(koiDiscount) - .subtract(collegeRankReward) - .subtract(drawPoolAmount) - .subtract(deliveryRankReward) + .subtract(refundAmount) + .subtract(afterSaleAmount) + .subtract(couponDiscount) .subtract(smsCost) .subtract(contentAuditCost) - .subtract(realNameCost); + .subtract(realNameCost) + .subtract(rewardTotalAmount) + .subtract(settledAmount); vo.setOrderCount((long) orders.size()); vo.setGrossFlowAmount(money(grossFlow)); @@ -210,9 +231,11 @@ public class AdminDataStatisticsController { vo.setKoiDiscountAmount(money(koiDiscount)); vo.setCouponDiscountAmount(money(couponDiscount)); vo.setOnTimeRate(onTimeRate(deliveries)); - vo.setCustomerAverageAmount(orders.isEmpty() ? BigDecimal.ZERO : money(customerAverageBase.divide(BigDecimal.valueOf(orders.size()), 4, RoundingMode.HALF_UP))); + vo.setCustomerAverageAmount(orders.isEmpty() ? BigDecimal.ZERO : money(goodsAndPackage.divide(BigDecimal.valueOf(orders.size()), 4, RoundingMode.HALF_UP))); vo.setHeadquartersDeductionAmount(money(headquartersDeduction)); - vo.setActualIncomeAmount(money(actualIncome)); + vo.setActualIncomeAmount(actualIncomeAvailable ? money(actualIncome) : BigDecimal.ZERO); + vo.setActualIncomeAvailable(actualIncomeAvailable); + vo.setSettledAmount(money(settledAmount)); vo.setSmsCount(smsCount); vo.setContentAuditCount(contentAuditCount); vo.setRealNameAuthCount(realNameAuthCount); @@ -243,6 +266,28 @@ public class AdminDataStatisticsController { .reduce(BigDecimal.ZERO, BigDecimal::add); } + private BigDecimal sumSettledAmount(String regionId, DateRange range) { + List settlementRecords = mallSettlementRecordMapper.selectList(new LambdaQueryWrapper() + .eq(StringUtils.isNotBlank(regionId), MallSettlementRecord::getRegionId, regionId) + .eq(MallSettlementRecord::getStatus, 1) + .ge(MallSettlementRecord::getCreateTime, range.start) + .le(MallSettlementRecord::getCreateTime, range.end)); + return settlementRecords.stream() + .map(MallSettlementRecord::getSettlementAmount) + .map(this::amount) + .reduce(BigDecimal.ZERO, BigDecimal::add); + } + + private boolean isRefundedOrder(MallOrder order, Set refundedOrderIds) { + return (order.getStatus() != null && order.getStatus() == 8) + || refundedOrderIds.contains(order.getId()); + } + + private boolean isActualIncomeAvailable(DateRange range) { + long days = ChronoUnit.DAYS.between(range.startDate, range.endDate) + 1; + return days <= 31 && range.endDate.isBefore(LocalDate.now()); + } + private BigDecimal onTimeRate(List deliveries) { if (deliveries == null || deliveries.isEmpty()) { return BigDecimal.ZERO; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java index 3c5b2a06..de1ea199 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/ShopController.java @@ -45,11 +45,13 @@ import cc.hiver.mall.service.ShopTakeawayService; import cc.hiver.mall.service.ShopUserService; import cc.hiver.mall.utils.UpdateUtils; import cn.hutool.core.text.CharSequenceUtil; +import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; +import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -57,10 +59,8 @@ import org.springframework.data.domain.PageImpl; import org.springframework.web.bind.annotation.*; import java.math.BigDecimal; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** @@ -71,6 +71,9 @@ import java.util.stream.Collectors; @Api(tags = "店铺管理接口") @RequestMapping("/hiver/app/shop/") public class ShopController { + private static final String GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX = "GROUP_BUY_DEFAULT_SHOP_PAGE:"; + private static final long GROUP_BUY_DEFAULT_PAGE_CACHE_SECONDS = 5 * 60L; + @Autowired private ShopService shopService; @@ -303,16 +306,35 @@ public class ShopController { @ApiOperation("多条件分页获取公司列表") public Result> getByCondition(Shop shop, PageVo pageVo) { + long queryStartTime = System.currentTimeMillis(); String regionId = shop.getRegionId(); boolean adminQuery = CharSequenceUtil.isNotBlank(shop.getShopIcon()); + String defaultPageCacheKey = buildDefaultGroupBuyPageCacheKey(shop, pageVo, adminQuery); + if (CharSequenceUtil.isNotBlank(defaultPageCacheKey)) { + String cacheJson = redisTemplateHelper.get(defaultPageCacheKey); + if (CharSequenceUtil.isNotBlank(cacheJson)) { + try { + ShopListPageCache cache = JSONUtil.toBean(cacheJson, ShopListPageCache.class); + if (cache != null && cache.getContent() != null) { + Page pageResult = new PageImpl<>(cache.getContent(), PageUtil.initPage(pageVo), cache.getTotalElements()); + return new ResultUtil>().setData(pageResult); + } + } catch (Exception e) { + redisTemplateHelper.delete(defaultPageCacheKey); + log.warn("饭团默认店铺列表缓存解析失败,已回源: key={}", defaultPageCacheKey, e); + } + } + } if (CharSequenceUtil.isNotBlank(regionId)) { List values = redisTemplateHelper.hValues("SHOP_CACHE:" + regionId); if (values != null && !values.isEmpty()) { List allShops = new ArrayList<>(); for (Object v : values) { - ShopCacheDTO dto = JSONUtil.toBean(v.toString(), ShopCacheDTO.class); + ShopCacheDTO dto = parseShopCacheBase(v.toString()); Shop s = dto.getShop(); - s.setProducts(dto.getProducts()); + if (s == null) { + continue; + } s.setShopTakeaway(dto.getShopTakeaway()); if (CharSequenceUtil.isNotBlank(shop.getShopArea())) { if (!shop.getShopArea().equals(s.getShopArea())) { @@ -458,6 +480,8 @@ public class ShopController { fillGroupDeliveryTime(pageList, regionId); Page pageResult = new PageImpl<>(pageList, PageUtil.initPage(pageVo), allShops.size()); + cacheDefaultGroupBuyPage(defaultPageCacheKey, pageList, allShops.size()); + logSlowShopListQuery("redis", queryStartTime, allShops.size(), pageList.size(), regionId); return new ResultUtil>().setData(pageResult); } } @@ -483,27 +507,84 @@ public class ShopController { } IPage productList = shopService.getShareList(productPageQuery); + Map> productsByShopId = new HashMap<>(); + if(productList.getRecords().size() > 0){ + productList.getRecords().forEach(productPageVO -> { + productsByShopId.computeIfAbsent(productPageVO.getShopId(), key -> new ArrayList<>()).add(productPageVO); + }); + } + Map takeawayByShopId = new HashMap<>(); + for(ShopTakeaway shopTakeawa : shopTakeaways) { + takeawayByShopId.put(shopTakeawa.getShopId(), shopTakeawa); + } page.getContent().forEach(e -> { - final List products = new ArrayList<>(); - if(productList.getRecords().size() > 0){ - productList.getRecords().forEach(productPageVO -> { - if (e.getId().equals(productPageVO.getShopId())) { - products.add(productPageVO); - } - }); - } - e.setProducts(products); - for(ShopTakeaway shopTakeawa : shopTakeaways) { - if (e.getId().equals(shopTakeawa.getShopId())) { - e.setShopTakeaway(shopTakeawa); - } - } + e.setProducts(productsByShopId.getOrDefault(e.getId(), new ArrayList<>())); + e.setShopTakeaway(takeawayByShopId.get(e.getId())); }); fillGroupDeliveryTime(page.getContent(), shop.getRegionId()); } + cacheDefaultGroupBuyPage(defaultPageCacheKey, page.getContent(), page.getTotalElements()); + logSlowShopListQuery("db", queryStartTime, page.getTotalElements(), page.getContent().size(), shop.getRegionId()); return new ResultUtil>().setData(page); } + private String buildDefaultGroupBuyPageCacheKey(Shop shop, PageVo pageVo, boolean adminQuery) { + if (shop == null || pageVo == null || adminQuery || CharSequenceUtil.isBlank(shop.getRegionId())) { + return null; + } + int pageNumber = pageVo.getPageNumber() > 0 ? pageVo.getPageNumber() : 1; + int pageSize = pageVo.getPageSize() > 0 ? pageVo.getPageSize() : 10; + if (pageNumber != 1 || pageSize != 10) { + return null; + } + if (!Integer.valueOf(1).equals(shop.getMerchantType())) { + return null; + } + if (shop.getIsStudent() != null || + CharSequenceUtil.isNotBlank(shop.getShopArea()) || + CharSequenceUtil.isNotBlank(shop.getShopType()) || + CharSequenceUtil.isNotBlank(shop.getKeyWord()) || + CharSequenceUtil.isNotBlank(shop.getShopName())) { + return null; + } + if (!"shoprank".equals(pageVo.getSort()) || !"desc".equalsIgnoreCase(pageVo.getOrder())) { + return null; + } + return GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX + shop.getRegionId() + ":" + pageSize; + } + + private ShopCacheDTO parseShopCacheBase(String cacheJson) { + JSONObject jsonObject = JSONUtil.parseObj(cacheJson); + ShopCacheDTO dto = new ShopCacheDTO(); + dto.setShop(jsonObject.getBean("shop", Shop.class)); + dto.setShopTakeaway(jsonObject.getBean("shopTakeaway", ShopTakeaway.class)); + return dto; + } + + private void logSlowShopListQuery(String source, long startTime, long totalElements, int pageSize, String regionId) { + long cost = System.currentTimeMillis() - startTime; + if (cost > 300) { + log.warn("app/shop/getByCondition slow source={}, cost={}ms, regionId={}, total={}, pageSize={}", + source, cost, regionId, totalElements, pageSize); + } + } + + private void cacheDefaultGroupBuyPage(String cacheKey, List content, long totalElements) { + if (CharSequenceUtil.isBlank(cacheKey) || content == null) { + return; + } + ShopListPageCache cache = new ShopListPageCache(); + cache.setContent(content); + cache.setTotalElements(totalElements); + redisTemplateHelper.set(cacheKey, JSONUtil.toJsonStr(cache), GROUP_BUY_DEFAULT_PAGE_CACHE_SECONDS, TimeUnit.SECONDS); + } + + @Data + private static class ShopListPageCache { + private List content; + private long totalElements; + } + private void fillGroupDeliveryTime(List shops, String regionId) { if (shops == null || shops.isEmpty()) { return; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/UserAddressController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/UserAddressController.java index 16e407dd..8575630f 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/UserAddressController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/UserAddressController.java @@ -36,7 +36,7 @@ public class UserAddressController { if (result) { // 如果传过来的就是默认地址,可以调用把其他的设置为非默认 if (userAddress.getIsDefault() != null && userAddress.getIsDefault() == 1) { - userAddressService.setDefaultAddress(userAddress.getId(), userAddress.getUserId()); + userAddressService.setDefaultAddress(userAddress.getId(), userAddress.getUserId(), userAddress.getRegionId()); } return ResultUtil.success("添加成功"); } else { @@ -52,12 +52,18 @@ public class UserAddressController { } // 不能修改别人的地址 UserAddress old = userAddressService.getById(userAddress.getId()); + if (old == null) { + return ResultUtil.error("地址不存在"); + } + if (StringUtils.isBlank(userAddress.getRegionId())) { + userAddress.setRegionId(old.getRegionId()); + } boolean result = userAddressService.updateById(userAddress); if (result) { // 如果改成了默认地址,则更新其他为非默认 if (userAddress.getIsDefault() != null && userAddress.getIsDefault() == 1) { - userAddressService.setDefaultAddress(userAddress.getId(), old.getUserId()); + userAddressService.setDefaultAddress(userAddress.getId(), old.getUserId(), userAddress.getRegionId()); } return ResultUtil.success("修改成功"); } else { @@ -81,11 +87,12 @@ public class UserAddressController { @RequestMapping(value = "/list", method = RequestMethod.GET) @ApiOperation(value = "获取当前用户的收货地址列表") - public Result list(@RequestParam("userId") String userId) { + public Result list(@RequestParam("userId") String userId, + @RequestParam(value = "regionId", required = false) String regionId) { if (userId == null || StringUtils.isEmpty(userId)) { return ResultUtil.error("未获取到当前登录用户信息"); } - List list = userAddressService.getAddressListByUserId(userId); + List list = userAddressService.getAddressListByUserId(userId, regionId); return new ResultUtil>().setData(list); } @@ -101,11 +108,13 @@ public class UserAddressController { @RequestMapping(value = "/setDefault", method = RequestMethod.POST) @ApiOperation(value = "设置默认收货地址") - public Result setDefault(@RequestParam("id") String id,@RequestParam("userId") String userId) { + public Result setDefault(@RequestParam("id") String id, + @RequestParam("userId") String userId, + @RequestParam(value = "regionId", required = false) String regionId) { if (StringUtils.isBlank(id)) { return ResultUtil.error("地址ID不能为空"); } - userAddressService.setDefaultAddress(id, userId); + userAddressService.setDefaultAddress(id, userId, regionId); return ResultUtil.success("设置成功"); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java index 4fd52d86..33084d9e 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerController.java @@ -2,6 +2,7 @@ package cc.hiver.mall.controller; import cc.hiver.core.common.constant.CommonConstant; import cc.hiver.core.common.constant.WorkerConstant; +import cc.hiver.core.common.redis.RedisTemplateHelper; import cc.hiver.core.common.utils.BeanUtils; import cc.hiver.core.common.utils.ResultUtil; import cc.hiver.core.common.utils.SecurityUtil; @@ -26,6 +27,7 @@ import cc.hiver.mall.utils.WaitOrderCacheUtil; import cc.hiver.mall.utils.WorkerOrderCacheUtil; import cc.hiver.mall.utils.WorkerRedisCacheUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.json.JSONUtil; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import io.swagger.annotations.Api; @@ -41,6 +43,7 @@ import org.springframework.web.bind.annotation.*; import javax.validation.Valid; import java.math.BigDecimal; import java.util.*; +import java.util.concurrent.TimeUnit; /** * 配送员管理接口 @@ -55,6 +58,9 @@ import java.util.*; @Transactional public class WorkerController { + private static final String ONLINE_WORKER_AREA_COUNT_CACHE_PREFIX = "WORKER_AREA_ONLINE_COUNT:"; + private static final long ONLINE_WORKER_AREA_COUNT_CACHE_SECONDS = 10L; + @Autowired private WorkerService workerService; @@ -85,6 +91,9 @@ public class WorkerController { @Autowired private SecurityUtil securityUtil; + @Autowired + private RedisTemplateHelper redisTemplateHelper; + @RequestMapping(value = "/getByCondition", method = RequestMethod.POST) @ApiOperation(value = "多条件分页获取订单列表") public Result> getByCondition(@RequestBody WorkerQueryVO worker) { @@ -255,6 +264,29 @@ public class WorkerController { return ResultUtil.success("操作成功"); } + @RequestMapping(value = "/admin/fullTime/{workerId}", method = RequestMethod.POST) + @ApiOperation(value = "后台设置配送员全职状态") + public Result changeFullTime(@ApiParam("配送员唯一id标识") @PathVariable String workerId, + @RequestParam("isFullTime") Integer isFullTime) { + Worker worker = workerService.get(workerId); + if (worker == null) { + return ResultUtil.error("配送员不存在"); + } + if (isFullTime == null || (isFullTime != 0 && isFullTime != 1)) { + return ResultUtil.error("全职状态参数错误"); + } + workerService.changeFullTime(workerId, isFullTime); + worker.setIsFullTime(isFullTime); + if (StrUtil.isNotBlank(worker.getRegion())) { + WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId()); + if (workerRedisVo != null) { + workerRedisVo.setWorker(worker); + workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo); + } + } + return ResultUtil.success("操作成功"); + } + /** * 配送员上线 * @@ -281,7 +313,7 @@ public class WorkerController { workerVO.setWorkerId(worker.getWorkerId()); worker.setIsOnLine(1); workerVO.setWorker(worker); - List workerRelaPriceList = workerRelaPriceService.selectByWorkerId(worker.getWorkerId()); + List workerRelaPriceList = workerRelaPriceService.selectByWorkerIdAndRegionId(worker.getWorkerId(), worker.getRegion()); workerVO.setWorkerRelaPriceList(workerRelaPriceList); workerRedisCacheUtil.put(worker.getRegion(),workerVO); return ResultUtil.success("上线成功"); @@ -401,6 +433,8 @@ public class WorkerController { WorkerRelaPrice matchedRule = null; for (WorkerRelaPrice rp : vo.getWorkerRelaPriceList()) { + // 缓存可能残留旧数据,规则也要限定当前校区 + if (StrUtil.isNotBlank(dto.getRegionId()) && !dto.getRegionId().equals(rp.getRegionId())) continue; // 规则必须接收指派 if (rp.getGetPushOrder() == null || rp.getGetPushOrder() != 1) continue; // 取货区域匹配 @@ -455,6 +489,7 @@ public class WorkerController { matchVO.setScore(w.getScore()); matchVO.setAvgTime(w.getAvgTime()); matchVO.setRebateAmount(w.getRebateAmount()); + matchVO.setIsFullTime(w.getIsFullTime()); matchVO.setHighFloorFee(w.getHighFloorFee()); matchVO.setDeliveryLocation(w.getDeliveryLocation()); matchVO.setOrderBkge(matchedRule.getOrderBkge()); @@ -478,6 +513,8 @@ public class WorkerController { comparator = Comparator.comparing(WorkerMatchVO::getAvgTime, Comparator.nullsLast(Comparator.naturalOrder())); } + comparator = Comparator.comparing(WorkerMatchVO::getIsFullTime, + Comparator.nullsLast(Comparator.reverseOrder())).thenComparing(comparator); // 二级排序:rebateAmount 倒序 comparator = comparator.thenComparing(WorkerMatchVO::getRebateAmount, Comparator.nullsLast(Comparator.reverseOrder())); @@ -527,6 +564,17 @@ public class WorkerController { @RequestMapping(value = "/getOnlineWorkerByArea", method = RequestMethod.GET) @ApiOperation(value = "查询各食堂区域在线配送员数量") public Result> getOnlineWorkerByArea(@RequestParam(value = "parentId")String parentId) { + String cacheKey = ONLINE_WORKER_AREA_COUNT_CACHE_PREFIX + parentId; + String cacheJson = redisTemplateHelper.get(cacheKey); + if (StrUtil.isNotBlank(cacheJson)) { + try { + List cacheList = JSONUtil.toList(cacheJson, WorkerAreaOnlineCountVO.class); + return new ResultUtil>().setData(cacheList); + } catch (Exception e) { + redisTemplateHelper.delete(cacheKey); + log.warn("在线配送员区域统计缓存解析失败,已回源: key={}", cacheKey, e); + } + } //parentId就是regionId List workerRedisVoList = workerRedisCacheUtil.getAll(parentId); if (workerRedisVoList != null && !workerRedisVoList.isEmpty()) { @@ -564,9 +612,11 @@ public class WorkerController { countVO.setOnlineWorkerCount(workers != null ? workers.size() : 0); result.add(countVO); } + redisTemplateHelper.set(cacheKey, JSONUtil.toJsonStr(result), ONLINE_WORKER_AREA_COUNT_CACHE_SECONDS, TimeUnit.SECONDS); return new ResultUtil>().setData(result); } List list = workerRelaPriceMapper.getOnlineWorkerCountByCanteenArea(parentId); + redisTemplateHelper.set(cacheKey, JSONUtil.toJsonStr(list), ONLINE_WORKER_AREA_COUNT_CACHE_SECONDS, TimeUnit.SECONDS); return new ResultUtil>().setData(list); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerRelaPriceController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerRelaPriceController.java index 76acb318..dc0b2bf6 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerRelaPriceController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/controller/WorkerRelaPriceController.java @@ -65,17 +65,20 @@ public class WorkerRelaPriceController { @RequestMapping(value = "/getByWorkerId", method = RequestMethod.GET) @ApiOperation("根据配送员id获取") - public Result getByProductId(@RequestParam(value = "workerId") String workerId) { + public Result getByProductId(@RequestParam(value = "workerId") String workerId, + @RequestParam(value = "regionId", required = false) String regionId) { WorkerRealPriceVo workerRealPriceVo = new WorkerRealPriceVo(); Worker worker = workerService.findByWorkerId(workerId); if(worker != null){ + regionId = resolveRegionId(regionId, worker); + workerRealPriceVo.setRegionId(regionId); workerRealPriceVo.setWorkerName(worker.getWorkerName()); workerRealPriceVo.setMobile(worker.getMobile()); workerRealPriceVo.setGetPushOrder(worker.getGetPushOrder()); workerRealPriceVo.setCardPicture(worker.getCardPicture()); workerRealPriceVo.setHighFloorFee(worker.getHighFloorFee()); workerRealPriceVo.setDeliveryLocation(worker.getDeliveryLocation()); - final List workerRelaPriceList = workerRelaPriceService.selectByWorkerId(workerId); + final List workerRelaPriceList = selectWorkerRelaPrices(workerId, regionId); workerRealPriceVo.setWorkerRelaPriceList(workerRelaPriceList); } return new ResultUtil().setData(workerRealPriceVo); @@ -86,18 +89,20 @@ public class WorkerRelaPriceController { public Result batchAdd(@RequestBody WorkerRealPriceVo workerRealPriceVo) { User user = userService.findById(workerRealPriceVo.getUserId()); Worker worker = workerService.findByUserId(workerRealPriceVo.getUserId()); + String regionId = resolveRegionId(workerRealPriceVo.getRegionId(), worker); + workerRealPriceVo.setRegionId(regionId); if (CharSequenceUtil.isNotBlank(workerRealPriceVo.getWorkerName())) { Long auditUserId = Long.valueOf(workerRealPriceVo.getUserId()); IeAuditResult audit = auditService.audit("worker-name", auditUserId, auditUserId, - workerRealPriceVo.getWorkerName(), worker.getRegion()); + workerRealPriceVo.getWorkerName(), regionId); if (Boolean.TRUE.equals(audit.getBlocked())) { throw new RuntimeException("配送员名称未通过审核,请换一个更合适的名称"); } workerRealPriceVo.setWorkerName(audit.getFilteredContent()); } - List workerRelaPriceList = workerRelaPriceService.selectByWorkerId(worker.getWorkerId()); + List workerRelaPriceList = selectWorkerRelaPrices(worker.getWorkerId(), regionId); //更新配送员信息 worker.setWorkerName(workerRealPriceVo.getWorkerName()); worker.setCardPicture(workerRealPriceVo.getCardPicture()); @@ -119,19 +124,20 @@ public class WorkerRelaPriceController { }); } workerRelaPrice.setWorkerId(worker.getWorkerId()); + workerRelaPrice.setRegionId(regionId); }); } if(workerRealPriceVo != null){ - workerRelaPriceService.deleteByWorkerId(worker.getWorkerId()); + deleteWorkerRelaPrices(worker.getWorkerId(), regionId); } final boolean b = workerRelaPriceService.saveBatch(workerRealPriceVo.getWorkerRelaPriceList()); if (b) { //更新缓存 - WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId()); + WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(regionId, worker.getWorkerId()); if(workerRedisVo != null){ workerRedisVo.setWorker(worker); - workerRedisVo.setWorkerRelaPriceList(workerRealPriceVo.getWorkerRelaPriceList()); - workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo); + workerRedisVo.setWorkerRelaPriceList(selectWorkerRelaPrices(worker.getWorkerId(), regionId)); + workerRedisCacheUtil.update(regionId, workerRedisVo); } return new ResultUtil().setData(user); } else { @@ -143,6 +149,7 @@ public class WorkerRelaPriceController { @ApiOperation("批量更新") public Result batchUpdate(@RequestBody WorkerRealPriceVo workerRealPriceVo) { Worker worker = workerService.findByUserId(workerRealPriceVo.getUserId()); + String regionId = resolveRegionId(workerRealPriceVo.getRegionId(), worker); //更新配送员信息 接受指派单 worker.setGetPushOrder(workerRealPriceVo.getGetPushOrder()); workerService.update(worker); @@ -150,23 +157,37 @@ public class WorkerRelaPriceController { final boolean b = workerRelaPriceService.saveOrUpdateBatch(workerRealPriceVo.getWorkerRelaPriceList()); if (b) { //更新缓存 - WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(worker.getRegion(), worker.getWorkerId()); + WorkerRedisVo workerRedisVo = workerRedisCacheUtil.get(regionId, worker.getWorkerId()); if(workerRedisVo != null){ workerRedisVo.getWorker().setGetPushOrder(workerRealPriceVo.getGetPushOrder()); - if(workerRedisVo.getWorkerRelaPriceList() != null){ - workerRedisVo.getWorkerRelaPriceList().forEach(workerRelaPrice -> { - workerRealPriceVo.getWorkerRelaPriceList().forEach(workerRelaPrice1 -> { - if(workerRelaPrice.getId() !=null && workerRelaPrice.getId().equals(workerRelaPrice1.getId())){ - workerRelaPrice.setGetPushOrder(workerRelaPrice1.getGetPushOrder()); - } - }); - }); - } - workerRedisCacheUtil.update(worker.getRegion(), workerRedisVo); + workerRedisVo.setWorkerRelaPriceList(selectWorkerRelaPrices(worker.getWorkerId(), regionId)); + workerRedisCacheUtil.update(regionId, workerRedisVo); } return ResultUtil.success("更新成功!"); } else { return ResultUtil.error("保存失败!"); } } + + private String resolveRegionId(String regionId, Worker worker) { + if (CharSequenceUtil.isNotBlank(regionId)) { + return regionId; + } + return worker == null ? null : worker.getRegion(); + } + + private List selectWorkerRelaPrices(String workerId, String regionId) { + if (CharSequenceUtil.isNotBlank(regionId)) { + return workerRelaPriceService.selectByWorkerIdAndRegionId(workerId, regionId); + } + return workerRelaPriceService.selectByWorkerId(workerId); + } + + private void deleteWorkerRelaPrices(String workerId, String regionId) { + if (CharSequenceUtil.isNotBlank(regionId)) { + workerRelaPriceService.deleteByWorkerIdAndRegionId(workerId, regionId); + return; + } + workerRelaPriceService.deleteByWorkerId(workerId); + } } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java index 3601d0c4..89e69aa7 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/dao/mapper/WorkerRelaPriceMapper.java @@ -18,8 +18,12 @@ public interface WorkerRelaPriceMapper extends BaseMapper { List selectByWorkerId(String workerId); + List selectByWorkerIdAndRegionId(@Param("workerId") String workerId, @Param("regionId") String regionId); + void deleteByWorkerId(String workerId); + void deleteByWorkerIdAndRegionId(@Param("workerId") String workerId, @Param("regionId") String regionId); + /** * 查询匹配条件的配送员列表(联查 t_worker_rela_price、t_worker、mall_delivery_order) *

    MyBatis-Plus 分页拦截器会根据 page 参数自动追加 COUNT 与 LIMIT/OFFSET

    diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallDeliveryOrder.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallDeliveryOrder.java index cb0f8db4..894835d6 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallDeliveryOrder.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/MallDeliveryOrder.java @@ -111,6 +111,10 @@ public class MallDeliveryOrder implements Serializable { private String transferPickupCode; @Transient @TableField(exist = false) + @ApiModelProperty(value = "商家出餐时间") + private Date shopMakeTime; + @Transient + @TableField(exist = false) @ApiModelProperty(value = "商品信息") private List goodsList; @TableField(exist = false) diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/UserAddress.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/UserAddress.java index ab710e42..647e6d7e 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/UserAddress.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/UserAddress.java @@ -18,6 +18,9 @@ public class UserAddress extends HiverBaseEntity { @ApiModelProperty(value = "用户ID/会员ID") private String userId; + @ApiModelProperty(value = "校区/区域ID") + private String regionId; + @ApiModelProperty(value = "收货人姓名") private String receiverName; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerRelaPrice.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerRelaPrice.java index bf51c915..2215d138 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerRelaPrice.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/entity/WorkerRelaPrice.java @@ -30,6 +30,9 @@ public class WorkerRelaPrice extends HiverBaseEntity { @ApiModelProperty(value = "配送员id") private String workerId; + @ApiModelProperty(value = "校区id") + private String regionId; + @ApiModelProperty(value = "取货区域id") private String getAreaId; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishCommentDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishCommentDTO.java index 419dd7a6..be4635e3 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishCommentDTO.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishCommentDTO.java @@ -5,6 +5,8 @@ import lombok.Data; @Data public class FishCommentDTO { private String goodsId; + private String userName; + private String userAvatar; private String parentId; private String replyToUserId; private String replyToUserName; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishPublishDTO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishPublishDTO.java index 6e437994..72f939b7 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishPublishDTO.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/dto/FishPublishDTO.java @@ -13,6 +13,8 @@ public class FishPublishDTO { private String publishType; private String regionId; private String regionName; + private String userName; + private String userAvatar; private String title; private String content; /** 富文本纯文字内容,用于字数限制和文字审核 */ diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/service/impl/FishMarketServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/service/impl/FishMarketServiceImpl.java index 6abf109d..080552c2 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/service/impl/FishMarketServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/fishmarket/service/impl/FishMarketServiceImpl.java @@ -5,6 +5,7 @@ import cc.hiver.core.common.sms.SmsUtil; import cc.hiver.core.entity.User; import cc.hiver.core.service.UserService; import cc.hiver.mall.billing.mq.BillingRecordProducer; +import cc.hiver.mall.campus.service.CampusService; import cc.hiver.mall.fishmarket.dto.*; import cc.hiver.mall.fishmarket.entity.*; import cc.hiver.mall.fishmarket.mapper.*; @@ -105,6 +106,8 @@ public class FishMarketServiceImpl implements FishMarketService { private StringRedisTemplate stringRedisTemplate; @Autowired(required = false) private FishMarketSmsReminderProducer smsReminderProducer; + @Autowired(required = false) + private CampusService campusService; private static final String SMS_PRODUCTTALK_TEMPLATE = SettingConstant.SMS_TYPE.valueOf("SMS_PRODUCTTALK").name(); private static final String FISH_CHAT_UNREAD_KEY_PREFIX = "fishmarket:unread:chat:"; @@ -259,8 +262,8 @@ public class FishMarketServiceImpl implements FishMarketService { throw new RuntimeException("只能编辑自己的商品"); } goods.setUserId(user.getId()); - goods.setUserName(defaultString(user.getMobile(), user.getUsername())); - goods.setUserAvatar(user.getAvatar()); + goods.setUserName(resolveDisplayName(dto.getUserName(), user)); + goods.setUserAvatar(resolveDisplayAvatar(dto.getUserAvatar(), user)); goods.setRegionId(dto.getRegionId()); goods.setRegionName(dto.getRegionName()); goods.setTitle(cleanText(dto.getTitle(), 60)); @@ -310,8 +313,8 @@ public class FishMarketServiceImpl implements FishMarketService { throw new RuntimeException("只能编辑自己的求购"); } want.setUserId(user.getId()); - want.setUserName(defaultString(user.getNickname(), user.getUsername())); - want.setUserAvatar(user.getAvatar()); + want.setUserName(resolveDisplayName(dto.getUserName(), user)); + want.setUserAvatar(resolveDisplayAvatar(dto.getUserAvatar(), user)); want.setRegionId(dto.getRegionId()); want.setRegionName(dto.getRegionName()); want.setTitle(cleanText(dto.getTitle(), 60)); @@ -481,8 +484,8 @@ public class FishMarketServiceImpl implements FishMarketService { comment.setReplyToUserId(StringUtils.defaultIfBlank(dto.getReplyToUserId(), parent == null ? null : parent.getUserId())); comment.setReplyToUserName(StringUtils.defaultIfBlank(dto.getReplyToUserName(), parent == null ? null : parent.getUserName())); comment.setUserId(user.getId()); - comment.setUserName(defaultString(user.getNickname(), user.getUsername())); - comment.setUserAvatar(user.getAvatar()); + comment.setUserName(resolveDisplayName(dto.getUserName(), user)); + comment.setUserAvatar(resolveDisplayAvatar(dto.getUserAvatar(), user)); comment.setContent(cleanText(dto.getContent(), 500)); comment.setLikeCount(0); comment.setAuditStatus(1); @@ -866,7 +869,15 @@ public class FishMarketServiceImpl implements FishMarketService { requireLogin(user); int chatUnread = sumUnreadHash(chatUnreadKey(user.getId())); int commentUnread = sumUnreadHash(commentUnreadKey(user.getId())); - return resultMap("chatUnread", chatUnread, "commentUnread", commentUnread, "fishUnread", chatUnread + commentUnread); + Map summary = resultMap("chatUnread", chatUnread, "commentUnread", commentUnread, "fishUnread", chatUnread + commentUnread); + if (campusService != null) { + try { + summary.putAll(campusService.unreadSummary(user)); + } catch (Exception e) { + log.warn("校园圈未读汇总扩展失败,不影响校园二手未读", e); + } + } + return summary; } @Override @@ -1756,13 +1767,6 @@ public class FishMarketServiceImpl implements FishMarketService { recordFishAuditBlock(bizType, bizId, userId, content, result); throw new RuntimeException(FISH_TEXT_AUDIT_REJECT_MESSAGE); } - if (aliyunModerationService != null) { - IeAliyunModerationResult aliyun = aliyunModerationService.checkText(content, regionId, bizType, bizId, parseLong(userId)); - if (aliyun != null && aliyun.isBlock()) { - recordFishAliyunBlock(bizType, bizId, userId, content, aliyun); - throw new RuntimeException(FISH_TEXT_AUDIT_REJECT_MESSAGE); - } - } } private void auditStrictText(String bizType, String bizId, String userId, String content, String regionId) { @@ -1772,12 +1776,10 @@ public class FishMarketServiceImpl implements FishMarketService { recordFishAuditBlock(bizType, bizId, userId, content, result); throw new RuntimeException(FISH_TEXT_AUDIT_REJECT_MESSAGE); } - if (aliyunModerationService != null) { - IeAliyunModerationResult aliyun = aliyunModerationService.checkText(content, regionId, bizType, bizId, parseLong(userId)); - if (aliyun != null && (aliyun.isBlock() || isMediumOrHigh(aliyun.getRiskLevel()))) { - recordFishAliyunBlock(bizType, bizId, userId, content, aliyun); - throw new RuntimeException(FISH_TEXT_AUDIT_REJECT_MESSAGE); - } + IeAliyunModerationResult aliyun = result == null ? null : result.getAliyunResult(); + if (aliyun != null && (aliyun.isBlock() || isMediumOrHigh(aliyun.getRiskLevel()))) { + recordFishAliyunBlock(bizType, bizId, userId, content, aliyun); + throw new RuntimeException(FISH_TEXT_AUDIT_REJECT_MESSAGE); } } @@ -2103,6 +2105,15 @@ public class FishMarketServiceImpl implements FishMarketService { return StringUtils.isNotBlank(value) ? value : StringUtils.defaultString(fallback); } + private String resolveDisplayName(String cachedName, User user) { + String name = defaultString(cachedName, user == null ? null : defaultString(user.getNickname(), user.getUsername())); + return cleanText(StringUtils.defaultIfBlank(name, "校园同学"), 20); + } + + private String resolveDisplayAvatar(String cachedAvatar, User user) { + return StringUtils.defaultIfBlank(cachedAvatar, user == null ? "" : StringUtils.defaultString(user.getAvatar())); + } + private Long parseLong(String value) { if (StringUtils.isBlank(value)) return null; try { diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeController.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeController.java index a7c14bb1..037625bf 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeController.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/controller/IeController.java @@ -201,6 +201,13 @@ public class IeController { return new ResultUtil>().setData(chatService.pageMessages(currentUserId(), roomId, pageNumber, pageSize)); } + @RequestMapping(value = "/rooms/{roomId}/read", method = RequestMethod.POST) + @ApiOperation("标记房间消息已读") + public Result markRoomRead(@PathVariable Long roomId) { + chatService.markRead(currentUserId(), roomId); + return ResultUtil.success("已读"); + } + @RequestMapping(value = "/rooms/{roomId}/streak", method = RequestMethod.GET) @ApiOperation("查询房间陪伴火花") public Result> roomStreak(@PathVariable Long roomId) { diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeAliyunModerationServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeAliyunModerationServiceImpl.java index f1c878a6..544e527b 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeAliyunModerationServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeAliyunModerationServiceImpl.java @@ -14,6 +14,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; +import java.util.List; import java.util.UUID; @Slf4j @@ -36,7 +37,7 @@ public class IeAliyunModerationServiceImpl implements IeAliyunModerationService @Value("${ie.aliyun.green.region:cn-shanghai}") private String region; - @Value("${ie.aliyun.green.text-service:chat_detection}") + @Value("${ie.aliyun.green.text-service:comment_detection_pro}") private String textService; @Value("${ie.aliyun.green.image-service:baselineCheck}") @@ -72,11 +73,14 @@ public class IeAliyunModerationServiceImpl implements IeAliyunModerationService } private IeAliyunModerationResult doCheckText(String content, String regionId, String bizType, String bizId, Long userId) { + if (useTextModerationPlus()) { + return doCheckTextPlus(content, regionId, bizType, bizId, userId); + } try { JSONObject parameters = new JSONObject(); parameters.set("content", content); TextModerationRequest request = new TextModerationRequest(); - request.setService(textService); + request.setService(textServiceCode()); request.setServiceParameters(parameters.toString()); TextModerationResponse response = getClient().textModerationWithOptions(request, runtime()); if (response == null || response.getBody() == null) { @@ -107,6 +111,62 @@ public class IeAliyunModerationServiceImpl implements IeAliyunModerationService } } + private boolean useTextModerationPlus() { + String service = textServiceCode().toLowerCase(); + return service.endsWith("_pro") || service.contains("comment_detection"); + } + + private String textServiceCode() { + return textService == null ? "" : textService.trim(); + } + + private IeAliyunModerationResult doCheckTextPlus(String content, String regionId, String bizType, String bizId, Long userId) { + try { + JSONObject parameters = new JSONObject(); + parameters.set("content", content); + parameters.set("dataId", UUID.randomUUID().toString()); + TextModerationPlusRequest request = new TextModerationPlusRequest(); + request.setService(textServiceCode()); + request.setServiceParameters(parameters.toString()); + TextModerationPlusResponse response = getClient().textModerationPlusWithOptions(request, runtime()); + if (response == null || response.getBody() == null) { + return recordContentAudit(regionId, "text", bizType, bizId, userId, + IeAliyunModerationResult.failed("empty text moderation plus response")); + } + TextModerationPlusResponseBody body = response.getBody(); + if (body.getCode() == null || body.getCode() != CODE_OK || body.getData() == null) { + return recordContentAudit(regionId, "text", bizType, bizId, userId, + IeAliyunModerationResult.failed("text moderation plus code=" + body.getCode() + ", msg=" + body.getMessage())); + } + TextModerationPlusResponseBody.TextModerationPlusResponseBodyData data = body.getData(); + String riskLevel = data.getRiskLevel(); + List resultList = data.getResult(); + if (CharSequenceUtil.isBlank(riskLevel) && (resultList == null || resultList.isEmpty())) { + return recordContentAudit(regionId, "text", bizType, bizId, userId, + IeAliyunModerationResult.pass(null, "text plus pass")); + } + StringBuilder labels = new StringBuilder(); + if (resultList != null) { + resultList.forEach(item -> { + if (item == null) return; + if (labels.length() > 0) labels.append(";"); + labels.append(item.getLabel()) + .append(":") + .append(item.getConfidence()) + .append(":") + .append(item.getRiskWords()); + }); + } + return recordContentAudit(regionId, "text", bizType, bizId, userId, + toFinalResult(riskLevel, "text plus riskLevel=" + riskLevel + ", labels=" + labels + + ", score=" + data.getScore() + ", service=" + textService)); + } catch (Exception e) { + log.warn("阿里云增强版文本审核调用失败", e); + return recordContentAudit(regionId, "text", bizType, bizId, userId, + IeAliyunModerationResult.failed("text moderation plus error: " + e.getMessage())); + } + } + @Override public IeAliyunModerationResult checkImage(String imageUrl, String regionId, String bizType, String bizId, Long userId) { if (CharSequenceUtil.isBlank(imageUrl)) { diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeSecurityAuditServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeSecurityAuditServiceImpl.java index e4a3494b..60cb74d3 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeSecurityAuditServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/service/impl/IeSecurityAuditServiceImpl.java @@ -42,8 +42,8 @@ public class IeSecurityAuditServiceImpl implements IeSecurityAuditService { "手机号", "私加", "私聊加", "进群", "刷单", "兼职日结", "网赚", "返利群", "贷款", "套现", "博彩", "赌博", "下注", "开盘", "卖号", "买号", "盗号", "洗钱", "跑分", "代实名", "出售银行卡", "收银行卡", "身份证代办", "发定位", "发照片", "要照片", "真实姓名", "宿舍号", "寝室号", "线下见面", - "来我宿舍", "去你宿舍", "约吗", "微信", "法轮功", "草一下", "c一下", "艹一下", "干一下", "逼", - "几把", "鸡吧", "代课" + "来我宿舍", "去你宿舍", "约吗", "微信", "法轮功", "草一下", "c一下", "艹一下", "干一下", + "几把", "鸡吧", "代课", "草拟吗", "双飞", "三飞", "3P", "小bb", "小BB", "小b" ); private volatile SensitiveWordBs cachedWordBs; @@ -123,6 +123,7 @@ public class IeSecurityAuditServiceImpl implements IeSecurityAuditService { if (!Boolean.TRUE.equals(result.getBlocked())) { IeAliyunModerationResult aliResult = aliyunModerationService.checkText(content, regionId, bizType, bizId == null ? null : String.valueOf(bizId), userId); + result.setAliyunResult(aliResult); if (aliResult.isBlock()) { result.setAuditStatus(IeConstants.AUDIT_BLOCK); result.setRiskLevel(IeConstants.RISK_HIGH); diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeAuditResult.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeAuditResult.java index 69c4fcd8..c05d17a9 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeAuditResult.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/ie/vo/IeAuditResult.java @@ -14,4 +14,5 @@ public class IeAuditResult { private Boolean blocked; private List hitWords = new ArrayList<>(); private List hitCategories = new ArrayList<>(); + private IeAliyunModerationResult aliyunResult; } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java index 0995eb0a..c0d6b4b9 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetAdminServiceImpl.java @@ -13,6 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.math.BigDecimal; +import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.List; @@ -90,6 +91,10 @@ public class PlanetAdminServiceImpl implements PlanetAdminService { return c.getTime(); } + private String genPeriodNo(Date drawTime) { + return new SimpleDateFormat("yyyyMMdd").format(drawTime); + } + private void refreshPoolRewardAmount(String poolId) { if (StringUtils.isEmpty(poolId)) { return; @@ -132,6 +137,10 @@ public class PlanetAdminServiceImpl implements PlanetAdminService { @Override public PlanetPool savePool(PlanetPool pool) { + if (pool.getDrawTime() == null) { + pool.setDrawTime(nextDrawTime(new Date())); + } + pool.setPeriodNo(genPeriodNo(pool.getDrawTime())); if (StringUtils.isEmpty(pool.getId())) { pool.setId(genId()); if (pool.getStatus() == null) { @@ -146,9 +155,6 @@ public class PlanetAdminServiceImpl implements PlanetAdminService { if (pool.getWinnerCount() == null) { pool.setWinnerCount(0); } - if (pool.getDrawTime() == null) { - pool.setDrawTime(nextDrawTime(new Date())); - } pool.setCreateTime(new Date()); pool.setUpdateTime(new Date()); poolMapper.insert(pool); diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java index d942f9dd..8963a967 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/planet/service/impl/PlanetDrawServiceImpl.java @@ -181,13 +181,14 @@ public class PlanetDrawServiceImpl implements PlanetDrawService { private PlanetPool createDefaultPool(String regionId) { final Date now = new Date(); + final Date drawTime = nextDrawTime(now); final PlanetPool pool = new PlanetPool(); pool.setId(UUID.randomUUID().toString().replace("-", "")); pool.setRegionId(regionId); - pool.setPeriodNo(genPeriodNo()); + pool.setPeriodNo(genPeriodNo(drawTime)); pool.setTitle("白嫖星球·本期瓜分"); pool.setPoolAmount(BigDecimal.ZERO); - pool.setDrawTime(nextDrawTime(now)); + pool.setDrawTime(drawTime); pool.setJoinCount(0); pool.setTicketTotal(0); pool.setWinnerCount(0); @@ -337,13 +338,14 @@ public class PlanetDrawServiceImpl implements PlanetDrawService { private void createNextPool(PlanetPool prev) { final Date now = new Date(); + final Date drawTime = nextDrawTime(now); final PlanetPool pool = new PlanetPool(); pool.setId(UUID.randomUUID().toString().replace("-", "")); pool.setRegionId(prev.getRegionId()); - pool.setPeriodNo(genPeriodNo()); + pool.setPeriodNo(genPeriodNo(drawTime)); pool.setTitle(prev.getTitle()); pool.setPoolAmount(prev.getPoolAmount()); - pool.setDrawTime(nextDrawTime(now)); + pool.setDrawTime(drawTime); pool.setJoinCount(0); pool.setTicketTotal(0); pool.setWinnerCount(0); @@ -564,8 +566,8 @@ public class PlanetDrawServiceImpl implements PlanetDrawService { return usable.get(usable.size() - 1); } - private String genPeriodNo() { - return "P" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()); + private String genPeriodNo(Date drawTime) { + return new SimpleDateFormat("yyyyMMdd").format(drawTime); } /** diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerMatchVO.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerMatchVO.java index 80578b82..266dc629 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerMatchVO.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerMatchVO.java @@ -40,6 +40,9 @@ public class WorkerMatchVO { @ApiModelProperty("交易保障金") private BigDecimal rebateAmount; + @ApiModelProperty("是否全职配送员:0否 1是") + private Integer isFullTime; + @ApiModelProperty("超高层额外费用") private BigDecimal highFloorFee; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerRealPriceVo.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerRealPriceVo.java index 6f404ad0..2db6fc9f 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerRealPriceVo.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/pojo/vo/WorkerRealPriceVo.java @@ -22,6 +22,9 @@ public class WorkerRealPriceVo implements Serializable { @ApiModelProperty(value = " 配送规则") private String userId; + @ApiModelProperty(value = "校区id") + private String regionId; + @ApiModelProperty(value = " 配送规则") private String workerName; diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/UserAddressService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/UserAddressService.java index c6987d48..082490cb 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/UserAddressService.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/UserAddressService.java @@ -14,15 +14,17 @@ public interface UserAddressService extends IService { /** * 查询用户收货地址列表 * @param userId 用户ID + * @param regionId 校区/区域ID * @return 地址列表 */ - List getAddressListByUserId(String userId); + List getAddressListByUserId(String userId, String regionId); /** * 设置默认地址 * @param id 地址ID * @param userId 用户ID + * @param regionId 校区/区域ID */ - void setDefaultAddress(String id, String userId); + void setDefaultAddress(String id, String userId, String regionId); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerRelaPriceService.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerRelaPriceService.java index 6d6b395c..74a159e3 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerRelaPriceService.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/service/mybatis/WorkerRelaPriceService.java @@ -11,5 +11,9 @@ public interface WorkerRelaPriceService extends IService { List selectByWorkerId(String workerId); + List selectByWorkerIdAndRegionId(String workerId, String regionId); + void deleteByWorkerId(String workerId); + + void deleteByWorkerIdAndRegionId(String workerId, String regionId); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java index b8c95baa..e9b5507b 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/ShopServiceImpl.java @@ -68,6 +68,8 @@ import java.util.Map; @Service @Transactional public class ShopServiceImpl implements ShopService { + private static final String GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX = "GROUP_BUY_DEFAULT_SHOP_PAGE:"; + @Autowired private ShopDao shopDao; @@ -328,6 +330,7 @@ public class ShopServiceImpl implements ShopService { if (CharSequenceUtil.isBlank(regionId)) { return; } + removeGroupBuyDefaultPageCache(regionId); Shop shop = shopDao.getById(shopId); if (shop == null) { removeShopCache(shopId, regionId); @@ -368,6 +371,7 @@ public class ShopServiceImpl implements ShopService { if (CharSequenceUtil.isBlank(regionId)) { return; } + removeGroupBuyDefaultPageCache(regionId); String redisKey = "SHOP_CACHE:" + regionId; redisTemplateHelper.hDelete(redisKey, shopId); @@ -376,4 +380,8 @@ public class ShopServiceImpl implements ShopService { redisTemplateHelper.delete(productsRedisKey); } + private void removeGroupBuyDefaultPageCache(String regionId) { + redisTemplateHelper.delete(GROUP_BUY_DEFAULT_PAGE_CACHE_PREFIX + regionId + ":10"); + } + } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java index 93b9c8fd..139aec81 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/MallDeliveryOrderServiceImpl.java @@ -689,14 +689,18 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl uw = new LambdaUpdateWrapper<>(); uw.eq(MallDeliveryOrder::getId, deliveryId) - .set(MallDeliveryOrder::getArriveTime, new Date()); + .eq(MallDeliveryOrder::getWorkerId, workerId) + .eq(MallDeliveryOrder::getStatus, STATUS_WAIT_PICKUP) + .set(MallDeliveryOrder::getArriveTime, arriveTime); this.update(uw); //更新缓存 MallDeliveryOrder delivery = workerOrderCacheUtil.get(workerId, deliveryId); if(delivery != null){ - delivery.setArriveTime(new Date()); + delivery.setArriveTime(arriveTime); workerOrderCacheUtil.update(workerId,delivery); } } @@ -1003,6 +1007,16 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl shopMakeTimeMap = new java.util.HashMap<>(); + List orders = mallOrderService.listByIds(new java.util.ArrayList<>(allOrderIdsToQuery)); + if (orders != null) { + for (MallOrder order : orders) { + if (order != null && StringUtils.isNotBlank(order.getId()) && order.getShopMakeTime() != null) { + shopMakeTimeMap.put(order.getId(), order.getShopMakeTime()); + } + } + } + // 3. 一次 IN 查询所有商品明细 List allGoods = mallOrderGoodsMapper.selectByOrderIds(new java.util.ArrayList<>(allOrderIdsToQuery)); @@ -1015,6 +1029,7 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl targetOrderIds = new java.util.HashSet<>(); if (StringUtils.isNotBlank(vo.getOrderId())) { targetOrderIds.add(vo.getOrderId()); + vo.setShopMakeTime(shopMakeTimeMap.get(vo.getOrderId())); } if (StringUtils.isNotBlank(vo.getGroupId()) && groupMap.containsKey(vo.getGroupId())) { MallOrderGroup group = groupMap.get(vo.getGroupId()); @@ -1305,11 +1320,14 @@ public class MallDeliveryOrderServiceImpl extends ServiceImpl implements UserAddressService { @Override - public List getAddressListByUserId(String userId) { + public List getAddressListByUserId(String userId, String regionId) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("user_id", userId); + queryWrapper.eq(StringUtils.isNotBlank(regionId), "region_id", regionId); // 按默认地址排序(1在前面),然后再按创建时间降序 queryWrapper.orderByDesc("is_default"); queryWrapper.orderByDesc("create_time"); @@ -44,10 +46,11 @@ public class UserAddressServiceImpl extends ServiceImpl updateWrapper = new UpdateWrapper<>(); updateWrapper.eq("user_id", userId); + updateWrapper.eq(StringUtils.isNotBlank(regionId), "region_id", regionId); updateWrapper.set("is_default", 0); this.update(updateWrapper); @@ -55,6 +58,7 @@ public class UserAddressServiceImpl extends ServiceImpl defaultWrapper = new UpdateWrapper<>(); defaultWrapper.eq("id", id); defaultWrapper.eq("user_id", userId); // 增加安全校验,防止修改别人的地址 + defaultWrapper.eq(StringUtils.isNotBlank(regionId), "region_id", regionId); defaultWrapper.set("is_default", 1); this.update(defaultWrapper); } diff --git a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/WorkerRelaPriceServiceImpl.java b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/WorkerRelaPriceServiceImpl.java index a1765fd2..46d01446 100644 --- a/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/WorkerRelaPriceServiceImpl.java +++ b/hiver-modules/hiver-mall/src/main/java/cc/hiver/mall/serviceimpl/mybatis/WorkerRelaPriceServiceImpl.java @@ -28,8 +28,18 @@ public class WorkerRelaPriceServiceImpl extends ServiceImpl selectByWorkerIdAndRegionId(String workerId, String regionId) { + return workerRelaPriceMapper.selectByWorkerIdAndRegionId(workerId, regionId); + } + @Override public void deleteByWorkerId(String workerId) { workerRelaPriceMapper.deleteByWorkerId(workerId); } + + @Override + public void deleteByWorkerIdAndRegionId(String workerId, String regionId) { + workerRelaPriceMapper.deleteByWorkerIdAndRegionId(workerId, regionId); + } } diff --git a/hiver-modules/hiver-mall/src/main/resources/db/campus_circle.sql b/hiver-modules/hiver-mall/src/main/resources/db/campus_circle.sql new file mode 100644 index 00000000..7867bd26 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/resources/db/campus_circle.sql @@ -0,0 +1,253 @@ +CREATE TABLE IF NOT EXISTS `campus_category` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `region_id` varchar(64) DEFAULT NULL COMMENT '校区/区域ID', + `region_name` varchar(100) DEFAULT NULL COMMENT '校区/区域名称', + `parent_code` varchar(40) NOT NULL COMMENT '一级频道:wall/game/daily/sport', + `code` varchar(80) NOT NULL COMMENT '分类或标签编码', + `name` varchar(80) NOT NULL COMMENT '分类或标签名称', + `description` varchar(300) DEFAULT NULL COMMENT '描述', + `icon` varchar(500) DEFAULT NULL COMMENT '图标', + `color` varchar(30) DEFAULT NULL COMMENT '颜色', + `category_type` varchar(20) DEFAULT 'category' COMMENT 'category分类,tag热门标签', + `sort_order` int DEFAULT 100 COMMENT '排序', + `status` int DEFAULT 1 COMMENT '状态:1启用,2停用', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_campus_category_region_parent_code` (`region_id`,`parent_code`,`code`,`category_type`), + KEY `idx_campus_category_region_parent_status_sort` (`region_id`,`parent_code`,`category_type`,`status`,`sort_order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈分类与热门标签'; + +CREATE TABLE IF NOT EXISTS `campus_post` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `region_id` varchar(64) DEFAULT NULL COMMENT '校区/区域ID', + `region_name` varchar(100) DEFAULT NULL COMMENT '校区/区域名称', + `user_id` varchar(64) NOT NULL COMMENT '发布人ID', + `user_name` varchar(100) DEFAULT NULL COMMENT '发布人昵称', + `user_avatar` varchar(500) DEFAULT NULL COMMENT '发布人头像', + `post_type` varchar(20) NOT NULL COMMENT 'wall校园墙,partner找搭子', + `partner_type` varchar(20) DEFAULT NULL COMMENT 'game/daily/sport', + `category_id` varchar(64) DEFAULT NULL COMMENT '二级分类ID', + `category_name` varchar(100) DEFAULT NULL COMMENT '二级分类名称', + `title` varchar(120) NOT NULL COMMENT '标题', + `content` text COMMENT '正文', + `content_text` varchar(2000) DEFAULT NULL COMMENT '纯文本正文', + `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图', + `images` text COMMENT '图片JSON', + `tags` varchar(1000) DEFAULT NULL COMMENT '标签JSON', + `location` varchar(200) DEFAULT NULL COMMENT '地点', + `activity_time` varchar(100) DEFAULT NULL COMMENT '期望时间', + `target_count` int DEFAULT NULL COMMENT '期望人数', + `joined_count` int DEFAULT 0 COMMENT '已联系/参与人数', + `contact_type` varchar(30) DEFAULT 'chat' COMMENT '联系方式:chat站内聊天', + `contact_wechat` varchar(80) DEFAULT NULL COMMENT '联系微信', + `contact_phone` varchar(20) DEFAULT NULL COMMENT '联系电话', + `browse_count` int DEFAULT 0 COMMENT '浏览数', + `like_count` int DEFAULT 0 COMMENT '点赞数', + `comment_count` int DEFAULT 0 COMMENT '评论数', + `chat_count` int DEFAULT 0 COMMENT '会话数', + `complaint_count` int DEFAULT 0 COMMENT '举报数', + `hot_score` int DEFAULT 0 COMMENT '热度分', + `status` int DEFAULT 1 COMMENT '业务状态:1正常,2下架,3删除', + `audit_status` int DEFAULT 0 COMMENT '审核状态:0待审核,1通过,2拒绝', + `audit_reason` varchar(500) DEFAULT NULL COMMENT '审核原因', + `off_shelf_reason` varchar(500) DEFAULT NULL COMMENT '下架原因', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_campus_post_region_type_status_hot` (`region_id`,`post_type`,`partner_type`,`status`,`audit_status`,`hot_score`,`create_time`), + KEY `idx_campus_post_category_status_time` (`category_id`,`status`,`audit_status`,`create_time`), + KEY `idx_campus_post_match_time` (`region_id`,`post_type`,`partner_type`,`category_id`,`status`,`audit_status`,`create_time`), + KEY `idx_campus_post_user_time` (`user_id`,`create_time`), + KEY `idx_campus_post_region_keyword` (`region_id`,`post_type`,`status`,`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈帖子'; + +CREATE TABLE IF NOT EXISTS `campus_comment` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `post_id` varchar(64) NOT NULL COMMENT '帖子ID', + `parent_id` varchar(64) DEFAULT NULL COMMENT '父评论ID', + `user_id` varchar(64) NOT NULL COMMENT '评论人ID', + `user_name` varchar(100) DEFAULT NULL COMMENT '评论人昵称', + `user_avatar` varchar(500) DEFAULT NULL COMMENT '评论人头像', + `reply_to_user_id` varchar(64) DEFAULT NULL COMMENT '回复目标用户ID', + `reply_to_user_name` varchar(100) DEFAULT NULL COMMENT '回复目标用户昵称', + `content` varchar(1000) NOT NULL COMMENT '评论内容', + `like_count` int DEFAULT 0 COMMENT '点赞数', + `audit_status` int DEFAULT 0 COMMENT '审核状态:0待审核,1通过,2拒绝', + `audit_reason` varchar(500) DEFAULT NULL COMMENT '审核原因', + `status` int DEFAULT 1 COMMENT '状态:1正常,2删除', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_campus_comment_post_status_time` (`post_id`,`status`,`audit_status`,`create_time`), + KEY `idx_campus_comment_parent_status_time` (`parent_id`,`status`,`audit_status`,`create_time`), + KEY `idx_campus_comment_user_time` (`user_id`,`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈评论'; + +CREATE TABLE IF NOT EXISTS `campus_like` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `user_id` varchar(64) NOT NULL COMMENT '用户ID', + `target_type` varchar(20) NOT NULL COMMENT 'post/comment/reaction', + `target_id` varchar(64) NOT NULL COMMENT '目标ID', + `reaction` varchar(20) DEFAULT NULL COMMENT '表情反应', + `status` int DEFAULT 1 COMMENT '1有效,2取消', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_campus_like_user_target` (`user_id`,`target_type`,`target_id`,`reaction`), + KEY `idx_campus_like_target` (`target_type`,`target_id`,`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈点赞和表情反应'; + +CREATE TABLE IF NOT EXISTS `campus_chat_session` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `session_id` varchar(64) DEFAULT NULL COMMENT '业务会话ID', + `post_id` varchar(64) NOT NULL COMMENT '帖子ID', + `owner_id` varchar(64) NOT NULL COMMENT '发布人ID', + `peer_user_id` varchar(64) NOT NULL COMMENT '联系用户ID', + `last_message_time` datetime DEFAULT NULL COMMENT '最后消息时间', + `last_message` varchar(500) DEFAULT NULL COMMENT '最后消息摘要', + `ended` int DEFAULT 0 COMMENT '是否结束', + `owner_deleted` int DEFAULT 0 COMMENT '发布人是否删除列表记录', + `peer_deleted` int DEFAULT 0 COMMENT '联系用户是否删除列表记录', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_campus_chat_post_peer` (`post_id`,`owner_id`,`peer_user_id`), + KEY `idx_campus_chat_owner_update` (`owner_id`,`last_message_time`), + KEY `idx_campus_chat_peer_update` (`peer_user_id`,`last_message_time`), + KEY `idx_campus_chat_session` (`session_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈聊天会话'; + +CREATE TABLE IF NOT EXISTS `campus_chat_message` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `session_id` varchar(64) NOT NULL COMMENT '业务会话ID', + `post_id` varchar(64) NOT NULL COMMENT '帖子ID', + `sender_id` varchar(64) NOT NULL COMMENT '发送人ID', + `receiver_id` varchar(64) NOT NULL COMMENT '接收人ID', + `message_type` int DEFAULT 1 COMMENT '1文本,2图片', + `content` varchar(1000) NOT NULL COMMENT '消息内容', + `audit_status` int DEFAULT 0 COMMENT '审核状态:0待审核,1通过,2拒绝', + `audit_reason` varchar(500) DEFAULT NULL COMMENT '审核原因', + `status` int DEFAULT 1 COMMENT '状态:1正常,2撤回/删除', + `read_flag` int DEFAULT 0 COMMENT '是否已读', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_campus_chat_message_session_time` (`session_id`,`create_time`), + KEY `idx_campus_chat_message_receiver_read` (`receiver_id`,`read_flag`,`create_time`), + KEY `idx_campus_chat_message_post_time` (`post_id`,`create_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈聊天消息'; + +CREATE TABLE IF NOT EXISTS `campus_complaint` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `region_id` varchar(64) DEFAULT NULL COMMENT '校区/区域ID', + `reporter_id` varchar(64) NOT NULL COMMENT '举报人ID', + `reporter_name` varchar(100) DEFAULT NULL COMMENT '举报人昵称', + `post_id` varchar(64) DEFAULT NULL COMMENT '帖子ID', + `comment_id` varchar(64) DEFAULT NULL COMMENT '评论ID', + `session_id` varchar(64) DEFAULT NULL COMMENT '会话ID', + `target_user_id` varchar(64) DEFAULT NULL COMMENT '被举报用户ID', + `complaint_type` varchar(30) NOT NULL COMMENT 'post/comment/chat', + `reason` varchar(1000) NOT NULL COMMENT '举报原因', + `images` text COMMENT '举报图片JSON', + `status` int DEFAULT 0 COMMENT '0待处理,1通过,2驳回', + `handle_result` varchar(1000) DEFAULT NULL COMMENT '处理结果', + `admin_id` varchar(64) DEFAULT NULL COMMENT '处理管理员ID', + `admin_name` varchar(100) DEFAULT NULL COMMENT '处理管理员名称', + `handle_time` datetime DEFAULT NULL COMMENT '处理时间', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_campus_complaint_region_status_time` (`region_id`,`status`,`create_time`), + KEY `idx_campus_complaint_post` (`post_id`), + KEY `idx_campus_complaint_target_user` (`target_user_id`,`status`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈投诉举报'; + +CREATE TABLE IF NOT EXISTS `campus_user_limit` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `region_id` varchar(64) DEFAULT NULL COMMENT '校区/区域ID', + `user_id` varchar(64) NOT NULL COMMENT '用户ID', + `user_name` varchar(100) DEFAULT NULL COMMENT '用户昵称', + `muted` int DEFAULT 0 COMMENT '是否禁言', + `publish_banned` int DEFAULT 0 COMMENT '是否禁止发布', + `permanent_banned` int DEFAULT 0 COMMENT '是否永久封禁', + `ban_reason` varchar(500) DEFAULT NULL COMMENT '封禁原因', + `start_time` datetime DEFAULT NULL COMMENT '开始时间', + `end_time` datetime DEFAULT NULL COMMENT '结束时间', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + UNIQUE KEY `uk_campus_user_limit_user` (`user_id`), + KEY `idx_campus_user_limit_region_user` (`region_id`,`user_id`), + KEY `idx_campus_user_limit_status_time` (`muted`,`publish_banned`,`permanent_banned`,`end_time`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈用户风控'; + +CREATE TABLE IF NOT EXISTS `campus_admin_log` ( + `id` varchar(64) NOT NULL COMMENT '主键', + `region_id` varchar(64) DEFAULT NULL COMMENT '校区/区域ID', + `admin_id` varchar(64) DEFAULT NULL COMMENT '管理员ID', + `admin_name` varchar(100) DEFAULT NULL COMMENT '管理员昵称', + `action_type` varchar(40) NOT NULL COMMENT '操作类型', + `target_type` varchar(40) NOT NULL COMMENT '目标类型', + `target_id` varchar(64) DEFAULT NULL COMMENT '目标ID', + `reason` varchar(500) DEFAULT NULL COMMENT '原因', + `before_data` text COMMENT '变更前', + `after_data` text COMMENT '变更后', + `create_by` varchar(64) DEFAULT NULL, + `create_time` datetime DEFAULT NULL, + `update_by` varchar(64) DEFAULT NULL, + `update_time` datetime DEFAULT NULL, + `del_flag` int DEFAULT 0, + PRIMARY KEY (`id`), + KEY `idx_campus_admin_log_region_time` (`region_id`,`create_time`), + KEY `idx_campus_admin_log_admin_time` (`admin_id`,`create_time`), + KEY `idx_campus_admin_log_target` (`target_type`,`target_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='校园圈后台操作日志'; + +INSERT INTO `campus_category` (`id`, `region_id`, `parent_code`, `code`, `name`, `description`, `category_type`, `sort_order`, `status`, `del_flag`) +SELECT REPLACE(UUID(), '-', ''), NULL, 'wall', 'campus_life', '校园生活', '日常分享、吐槽和新鲜事', 'category', 10, 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `campus_category` WHERE `region_id` IS NULL AND `parent_code`='wall' AND `code`='campus_life' AND `category_type`='category'); + +INSERT INTO `campus_category` (`id`, `region_id`, `parent_code`, `code`, `name`, `description`, `category_type`, `sort_order`, `status`, `del_flag`) +SELECT REPLACE(UUID(), '-', ''), NULL, 'game', 'moba', 'MOBA开黑', '王者、LOL手游等组队开黑', 'category', 10, 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `campus_category` WHERE `region_id` IS NULL AND `parent_code`='game' AND `code`='moba' AND `category_type`='category'); + +INSERT INTO `campus_category` (`id`, `region_id`, `parent_code`, `code`, `name`, `description`, `category_type`, `sort_order`, `status`, `del_flag`) +SELECT REPLACE(UUID(), '-', ''), NULL, 'daily', 'study', '学习搭子', '自习、备考、打卡监督', 'category', 10, 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `campus_category` WHERE `region_id` IS NULL AND `parent_code`='daily' AND `code`='study' AND `category_type`='category'); + +INSERT INTO `campus_category` (`id`, `region_id`, `parent_code`, `code`, `name`, `description`, `category_type`, `sort_order`, `status`, `del_flag`) +SELECT REPLACE(UUID(), '-', ''), NULL, 'sport', 'ball', '球类运动', '篮球、羽毛球、乒乓球等', 'category', 10, 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `campus_category` WHERE `region_id` IS NULL AND `parent_code`='sport' AND `code`='ball' AND `category_type`='category'); + +INSERT INTO `campus_category` (`id`, `region_id`, `parent_code`, `code`, `name`, `description`, `category_type`, `sort_order`, `status`, `del_flag`) +SELECT REPLACE(UUID(), '-', ''), NULL, 'game', 'honor_of_kings', '王者荣耀', '热门游戏标签', 'tag', 10, 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `campus_category` WHERE `region_id` IS NULL AND `parent_code`='game' AND `code`='honor_of_kings' AND `category_type`='tag'); + +INSERT INTO `campus_category` (`id`, `region_id`, `parent_code`, `code`, `name`, `description`, `category_type`, `sort_order`, `status`, `del_flag`) +SELECT REPLACE(UUID(), '-', ''), NULL, 'sport', 'basketball', '篮球', '热门运动标签', 'tag', 10, 1, 0 +WHERE NOT EXISTS (SELECT 1 FROM `campus_category` WHERE `region_id` IS NULL AND `parent_code`='sport' AND `code`='basketball' AND `category_type`='tag'); diff --git a/hiver-modules/hiver-mall/src/main/resources/db/user_address_region_id.sql b/hiver-modules/hiver-mall/src/main/resources/db/user_address_region_id.sql new file mode 100644 index 00000000..d2e28be7 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/resources/db/user_address_region_id.sql @@ -0,0 +1,3 @@ +ALTER TABLE `t_user_address` + ADD COLUMN `region_id` varchar(64) DEFAULT NULL COMMENT '校区/区域ID' AFTER `user_id`, + ADD INDEX `idx_user_address_user_region_default_time` (`user_id`, `region_id`, `is_default`, `create_time`); diff --git a/hiver-modules/hiver-mall/src/main/resources/db/worker_full_time.sql b/hiver-modules/hiver-mall/src/main/resources/db/worker_full_time.sql new file mode 100644 index 00000000..7ff903b0 --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/resources/db/worker_full_time.sql @@ -0,0 +1,3 @@ +-- 为 t_worker 表添加全职配送员标识 +ALTER TABLE `t_worker` + ADD COLUMN `is_full_time` TINYINT NULL DEFAULT 0 COMMENT '是否全职配送员:0否 1是' AFTER `get_push_order`; diff --git a/hiver-modules/hiver-mall/src/main/resources/db/worker_rela_price_region_id.sql b/hiver-modules/hiver-mall/src/main/resources/db/worker_rela_price_region_id.sql new file mode 100644 index 00000000..ef9b45ad --- /dev/null +++ b/hiver-modules/hiver-mall/src/main/resources/db/worker_rela_price_region_id.sql @@ -0,0 +1,12 @@ +-- 为配送员接单规则添加校区维度 +ALTER TABLE `t_worker_rela_price` + ADD COLUMN `region_id` VARCHAR(64) NULL COMMENT '校区id' AFTER `worker_id`; + +-- 回填历史规则所属校区 +UPDATE `t_worker_rela_price` wrp + INNER JOIN `t_worker` w ON w.`worker_id` = wrp.`worker_id` +SET wrp.`region_id` = w.`region` +WHERE wrp.`region_id` IS NULL OR wrp.`region_id` = ''; + +CREATE INDEX `idx_worker_rela_price_worker_region` + ON `t_worker_rela_price` (`worker_id`, `region_id`); diff --git a/hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml b/hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml index 5f11e4c6..574392ab 100644 --- a/hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml +++ b/hiver-modules/hiver-mall/src/main/resources/mapper/MallDeliveryOrderMapper.xml @@ -104,9 +104,9 @@ AND EXISTS ( - SELECT 1 FROM mall_order o - WHERE o.id = d.order_id - AND o.shop_delivery = #{q.shopDelivery} + SELECT 1 FROM mall_order mo + WHERE mo.id = d.order_id + AND mo.shop_delivery = #{q.shopDelivery} ) @@ -162,6 +162,11 @@ d.transfer_delivery,d.transfer_address_id,d.transfer_address_name, d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code FROM mall_delivery_order d + LEFT JOIN ( + SELECT delivery_order_id, MAX(reassign_time) AS latest_reassign_time + FROM mall_delivery_order_reassign_record + GROUP BY delivery_order_id + ) rr ON rr.delivery_order_id = d.id AND d.status = 0 @@ -170,6 +175,9 @@ AND d.worker_id IS NOT NULL AND d.worker_id != '' + + AND d.worker_id = #{q.workerId} + AND (d.worker_id IS NULL OR d.worker_id = '') @@ -198,6 +206,9 @@ ORDER BY + (CASE WHEN d.must_finish_time IS NOT NULL AND d.must_finish_time < NOW() THEN 0 ELSE 1 END) ASC, + (CASE WHEN rr.latest_reassign_time IS NULL THEN 1 ELSE 0 END) ASC, + rr.latest_reassign_time DESC, d.delivery_fee DESC, @@ -255,9 +266,9 @@ AND EXISTS ( - SELECT 1 FROM mall_order o - WHERE o.id = d.order_id - AND o.shop_delivery = #{q.shopDelivery} + SELECT 1 FROM mall_order mo + WHERE mo.id = d.order_id + AND mo.shop_delivery = #{q.shopDelivery} ) @@ -287,6 +298,12 @@ d.transfer_delivery,d.transfer_address_id,d.transfer_address_name, d.transfer_arrive_time,d.transfer_arrive_image,d.transfer_pickup_code FROM mall_delivery_order d + LEFT JOIN mall_order o ON o.id = d.order_id + LEFT JOIN ( + SELECT delivery_order_id, MAX(reassign_time) AS latest_reassign_time + FROM mall_delivery_order_reassign_record + GROUP BY delivery_order_id + ) rr ON rr.delivery_order_id = d.id d.status not in (-1,4) @@ -327,9 +344,9 @@ AND EXISTS ( - SELECT 1 FROM mall_order o - WHERE o.id = d.order_id - AND o.shop_delivery = #{q.shopDelivery} + SELECT 1 FROM mall_order mo + WHERE mo.id = d.order_id + AND mo.shop_delivery = #{q.shopDelivery} ) @@ -339,7 +356,12 @@ AND d.transfer_arrive_time IS NULL - ORDER BY (CASE WHEN d.must_finish_time IS NULL THEN 1 ELSE 0 END) ASC, + ORDER BY (CASE WHEN o.shop_make_time IS NULL THEN 1 ELSE 0 END) ASC, + o.shop_make_time ASC, + (CASE WHEN d.must_finish_time IS NOT NULL AND d.must_finish_time < NOW() THEN 0 ELSE 1 END) ASC, + (CASE WHEN rr.latest_reassign_time IS NULL THEN 1 ELSE 0 END) ASC, + rr.latest_reassign_time DESC, + (CASE WHEN d.must_finish_time IS NULL THEN 1 ELSE 0 END) ASC, d.must_finish_time ASC,d.delivery_fee DESC @@ -393,9 +415,9 @@ AND EXISTS ( - SELECT 1 FROM mall_order o - WHERE o.id = d.order_id - AND o.shop_delivery = #{q.shopDelivery} + SELECT 1 FROM mall_order mo + WHERE mo.id = d.order_id + AND mo.shop_delivery = #{q.shopDelivery} ) @@ -483,6 +505,9 @@ AND d.worker_id IS NOT NULL AND d.worker_id != '' AND d.status = 0 + + AND d.worker_id = #{q.workerId} + AND d.region_id = #{q.regionId} diff --git a/hiver-modules/hiver-mall/src/main/resources/mapper/WorkerRelaPriceMapper.xml b/hiver-modules/hiver-mall/src/main/resources/mapper/WorkerRelaPriceMapper.xml index 9c3a0137..a0dd8c25 100644 --- a/hiver-modules/hiver-mall/src/main/resources/mapper/WorkerRelaPriceMapper.xml +++ b/hiver-modules/hiver-mall/src/main/resources/mapper/WorkerRelaPriceMapper.xml @@ -9,6 +9,7 @@ + @@ -20,17 +21,17 @@ - id, create_by, create_time, del_flag, update_by, update_time, worker_id, get_area_id, + id, create_by, create_time, del_flag, update_by, update_time, worker_id, region_id, get_area_id, get_area_name, put_area_id, put_area_name, remark, order_type, order_bkge, get_push_order - + insert into t_worker_rela_price (id, create_by, create_time, del_flag, update_by, update_time, - worker_id, get_area_id, get_area_name, put_area_id, put_area_name, remark, order_type, order_bkge, get_push_order) + worker_id, region_id, get_area_id, get_area_name, put_area_id, put_area_name, remark, order_type, order_bkge, get_push_order) values (#{id,jdbcType=VARCHAR}, #{createBy,jdbcType=VARCHAR}, #{createTime,jdbcType=TIMESTAMP}, #{delFlag,jdbcType=INTEGER}, #{updateBy,jdbcType=VARCHAR}, #{updateTime,jdbcType=TIMESTAMP}, - #{workerId,jdbcType=VARCHAR}, #{getAreaId,jdbcType=VARCHAR}, #{getAreaName,jdbcType=VARCHAR}, + #{workerId,jdbcType=VARCHAR}, #{regionId,jdbcType=VARCHAR}, #{getAreaId,jdbcType=VARCHAR}, #{getAreaName,jdbcType=VARCHAR}, #{putAreaId,jdbcType=VARCHAR}, #{putAreaName,jdbcType=VARCHAR}, #{remark,jdbcType=VARCHAR}, #{orderType,jdbcType=INTEGER}, #{orderBkge,jdbcType=DECIMAL}, #{getPushOrder,jdbcType=INTEGER}) @@ -43,12 +44,27 @@ where worker_id = #{workerId,jdbcType=VARCHAR} + + delete from t_worker_rela_price where worker_id = #{workerId,jdbcType=VARCHAR} + + delete + from t_worker_rela_price + where worker_id = #{workerId,jdbcType=VARCHAR} + and region_id = #{regionId,jdbcType=VARCHAR} + +