zhangmei
2025-02-14 728a54cc02ff66e38bc8719ecc0f3155e597084c
Merge branch 'master' of https://gitee.com/xiaochen991015/xizang into xizang-changyun
62个文件已修改
35个文件已添加
5155 ■■■■ 已修改文件
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TBillController.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TCheckAcceptRecordController.java 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TContractController.java 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TFaultAreaDicController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TFaultRepairMessageController.java 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TItemTypeController.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/TaskUtil.java 496 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application-test.yml 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/COSController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TBillController.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TFaultRepairMessageController.java 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TInformationController.java 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/WxLoginController.java 255 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/TencentCosUtil.java 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/resources/application-prod.yml 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/resources/application-test.yml 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-applet/src/main/resources/mybatis-config.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/constant/DictConstants.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/TTenantResp.java 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUserApplet.java 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/exception/ServiceException.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/utils/Constants.java 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/utils/HttpUtils.java 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/redis/service/RedisService.java 273 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/pom.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/dto/TFaultRepairMessageDTO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TCheckAcceptRecordMapper.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TDeptToUserMapper.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TFaultAreaDicMapper.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TFaultRepairMessageMapper.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TItemTypeMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TCheckAcceptRecord.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TContract.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TFaultRepairMessage.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TInformation.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/model/TTenant.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/query/SysUserQuery.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/query/TCheckAcceptRecordQuery.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/query/TFaultRepairMessageQuery.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TCheckAcceptRecordService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TFaultAreaDicService.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TFaultRepairMessageService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TItemTypeService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/TTenantService.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java 69 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TCheckAcceptRecordServiceImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TContractServiceImpl.java 514 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TFaultAreaDicServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TFaultRepairMessageServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TInformationServiceImpl.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TItemTypeServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTenantServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/AccessTokenRespBody.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/Code2SessionRespBody.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/RespBody.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resq/Code2SessionResqBody.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletPhoneEncrypteData.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserDecodeData.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/Watermark.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/SHA1.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WebUtils.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCache.java 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCacheTemplate.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxException.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxJsonUtils.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxUtils.java 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/FaultConcatInfoVO.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/SysUserVO.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/TBillVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/TCheckAcceptRecordVO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/TFaultAreaDicVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/TFaultRepairMessageVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/vo/TItemTypeVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TCheckAcceptRecordMapper.xml 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TContractMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TDeptToUserMapper.xml 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TFaultAreaDicMapper.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TFaultRepairMessageMapper.xml 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TItemMapper.xml 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/TItemTypeMapper.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TBillController.java
@@ -34,6 +34,9 @@
    @Autowired
    TBillService tBillService;
    @PreAuthorize("@ss.hasPermi('system:bill:list')")
    @PostMapping("list")
    @ApiOperation("分页查询账单列表")
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TCheckAcceptRecordController.java
@@ -1,9 +1,28 @@
package com.ruoyi.web.controller.api;
import org.springframework.web.bind.annotation.RequestMapping;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.model.TCheckAcceptRecord;
import com.ruoyi.system.model.TContract;
import com.ruoyi.system.model.THouse;
import com.ruoyi.system.query.TCheckAcceptRecordQuery;
import com.ruoyi.system.service.TCheckAcceptRecordService;
import com.ruoyi.system.service.TContractService;
import com.ruoyi.system.service.THouseService;
import com.ruoyi.system.vo.TCheckAcceptRecordVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * <p>
@@ -13,9 +32,110 @@
 * @author xiaochen
 * @since 2025-01-17
 */
@Api(tags = "验收记录管理")
@RestController
@RequestMapping("/t-check-accept-record")
public class TCheckAcceptRecordController {
    private final TCheckAcceptRecordService checkAcceptRecordService;
    private final TContractService contractService;
    private final THouseService houseService;
    @Autowired
    public TCheckAcceptRecordController(TCheckAcceptRecordService checkAcceptRecordService, TContractService contractService, THouseService houseService) {
        this.checkAcceptRecordService = checkAcceptRecordService;
        this.contractService = contractService;
        this.houseService = houseService;
    }
    /**
     * 获取验收记录管理列表
     */
    @PreAuthorize("@ss.hasPermi('system:accept:list')")
    @ApiOperation(value = "获取验收记录分页列表")
    @PostMapping(value = "/pageList")
    public R<PageInfo<TCheckAcceptRecordVO>> pageList(@RequestBody TCheckAcceptRecordQuery query) {
        return R.ok(checkAcceptRecordService.pageList(query));
    }
    /**
     * 通过合同id查询房屋信息
     */
    @ApiOperation(value = "通过合同id查询房屋信息")
    @PostMapping(value = "/getHouseByContractId")
    public R<THouse> getHouseByContractId(@RequestParam String contractId) {
        TContract contract = contractService.getById(contractId);
        THouse house = houseService.getById(contract.getHouseId());
        return R.ok(house);
    }
    /**
     * 通过房屋id查询合同信息
     */
    @ApiOperation(value = "通过房屋id查询合同信息")
    @PostMapping(value = "/getContractByHouseId")
    public R<TContract> getContractByHouseId(@RequestParam String houseId) {
        TContract contract = contractService.getOne(Wrappers.lambdaQuery(TContract.class)
                .eq(TContract::getHouseId, houseId)
                .orderByDesc(TContract::getCreateTime)
                .in(TContract::getStatus, 4, 6, 7)
                .last("LIMIT 1"));
        return R.ok(contract);
    }
    /**
     * 添加验收记录管理
     */
    @PreAuthorize("@ss.hasPermi('system:accept:add')")
    @Log(title = "验收记录信息-新增验收记录", businessType = BusinessType.INSERT)
    @ApiOperation(value = "添加验收记录")
    @PostMapping(value = "/add")
    public R<Boolean> add(@Validated @RequestBody TCheckAcceptRecord dto) {
        // 添加验收记录
        checkAcceptRecordService.save(dto);
        // TODO 生成结算帐单
        return R.ok();
    }
    /**
     * 查看验收记录详情
     */
    @PreAuthorize("@ss.hasPermi('system:accept:detail')")
    @ApiOperation(value = "查看验收记录详情")
    @GetMapping(value = "/getDetailById")
    public R<TCheckAcceptRecordVO> getDetailById(@RequestParam String id) {
        TCheckAcceptRecord checkAcceptRecord = checkAcceptRecordService.getById(id);
        TCheckAcceptRecordVO checkAcceptRecordVO = new TCheckAcceptRecordVO();
        BeanUtils.copyProperties(checkAcceptRecord, checkAcceptRecordVO);
        // 查询合同信息
        checkAcceptRecordVO.setContract(contractService.getById(checkAcceptRecord.getContractId()));
        // 查询房屋信息
        checkAcceptRecordVO.setHouse(houseService.getById(checkAcceptRecord.getHouseId()));
        return R.ok(checkAcceptRecordVO);
    }
    /**
     * 删除验收记录
     */
    @PreAuthorize("@ss.hasPermi('system:accept:delete')")
    @Log(title = "验收记录信息-删除验收记录", businessType = BusinessType.DELETE)
    @ApiOperation(value = "删除验收记录")
    @DeleteMapping(value = "/deleteById")
    public R<Boolean> deleteById(@RequestParam String id) {
        return R.ok(checkAcceptRecordService.removeById(id));
    }
    /**
     * 批量删除验收记录
     */
    @PreAuthorize("@ss.hasPermi('system:accept:delete')")
    @Log(title = "验收记录信息-删除验收记录", businessType = BusinessType.DELETE)
    @ApiOperation(value = "批量删除验收记录")
    @DeleteMapping(value = "/deleteByIds")
    public R<Boolean> deleteByIds(@RequestBody List<String> ids) {
        return R.ok(checkAcceptRecordService.removeByIds(ids));
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TContractController.java
@@ -47,7 +47,9 @@
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
/**
@@ -72,6 +74,109 @@
    private TBillService billService;
    @Autowired
    private TCheckAcceptRecordService checkAcceptRecordService;
    @ApiOperation(value = "测试生成账单")
    @PostMapping(value = "/testBill")
    public R testBill(String id) {
        TContract contract = contractService.getById(id);
        // 查询所有已签订的合同并且未生成第一笔账单的
        List<TBill> bills = new ArrayList<>();
        List<TContractRentType> contractRentTypes = contractRentTypeService.list();
            contract.setFirstPayTime(contract.getStartTime().plusDays(10));
            // 第一次应缴费日期
            LocalDateTime firstPayTime = contract.getStartTime().plusDays(10).withHour(0).withMinute(0).withSecond(0);
            TBill rentBill = new TBill();
            rentBill.setContractId(contract.getId());
            rentBill.setContractNumber(contract.getContractNumber());
            rentBill.setPayableFeesTime(firstPayTime);
            rentBill.setPayFeesStatus("1");
            rentBill.setBillType("1");
            rentBill.setStartTime(contract.getStartPayTime());
            TContractRentType tContractRentType = contractRentTypes.stream().filter(e -> e.getContractId().equals(contract.getId())).findFirst().orElse(null);
            if (tContractRentType!=null && contract.getStartPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(tContractRentType.getChangeTime())){
                // 计算租金变动的天数
                long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getStartPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12))+1L;
                contract.setChangeTime(LocalDateTime.now());
                // 递增递减的租金
                BigDecimal contractRentTypeMoney = new BigDecimal("0");
                // 不递增递减的租金
                BigDecimal originalMoney = new BigDecimal("0");
                // 原租金
                switch (tContractRentType.getIncreasingDecreasingType()){
                    case 1:
                        switch (tContractRentType.getIncreasingDecreasing()){
                            case 1:
                                contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                contract.setChangeRent(contractRentTypeMoney);
                                break;
                            case 2:
                                contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                contract.setChangeRent(contractRentTypeMoney);
                                break;
                        }
                        break;
                    case 2:
                        switch (tContractRentType.getIncreasingDecreasing()){
                            case 1:
                                contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                contract.setChangeRent(contractRentTypeMoney);
                                break;
                            case 2:
                                contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                contract.setChangeRent(contractRentTypeMoney);
                                break;
                        }
                        break;
                }
                // 不需要涨租金的时间段
                if (contract.getFirstPayTime().isBefore(tContractRentType.getChangeTime())){
                    long originalDays = ChronoUnit.DAYS.between(contract.getFirstPayTime(), tContractRentType.getChangeTime());
                    originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                            .multiply(new BigDecimal(originalDays));
                    rentBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                    rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney());
                }else{
                    rentBill.setPayableFeesMoney(contractRentTypeMoney);
                    rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney());
                }
                if (contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(contract.getEndTime())){
                    rentBill.setEndTime(contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
                }else{
                    rentBill.setEndTime(contract.getEndTime());
                }
            }else{
                if (contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(contract.getEndTime())){
                    rentBill.setEndTime(contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
                }else{
                    rentBill.setEndTime(contract.getEndTime());
                }
                // 不走递增递减
                long allDays = ChronoUnit.DAYS.between(contract.getFirstPayTime(), rentBill.getEndTime());
                rentBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
                rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney());
            }
            // 租金账单
            bills.add(rentBill);
            // 押金账单
            TBill depositBill = new TBill();
            depositBill.setContractId(contract.getId());
            depositBill.setContractNumber(contract.getContractNumber());
            depositBill.setPayableFeesMoney(contract.getDeposit());
            depositBill.setOutstandingMoney(depositBill.getPayableFeesMoney());
            depositBill.setPayableFeesTime(firstPayTime);
            depositBill.setPayFeesStatus("1");
            depositBill.setBillType("2");
        contractService.updateById(contract);
        billService.save(rentBill);
        billService.save(depositBill);
        return R.ok();
    }
    @ApiOperation(value = "获取合同分页列表")
    @PostMapping(value = "/contractList")
    @PreAuthorize("@ss.hasPermi('system:contract:list')")
@@ -84,6 +189,7 @@
    @PostMapping(value = "/addContract")
    @PreAuthorize("@ss.hasPermi('system:contract:add')")
    public R<Boolean> addContract(@Validated @RequestBody TContractDTO dto) {
        dto.setChangeRent(dto.getMonthRent());
        contractService.save(dto);
        if (dto.getIsIncreasing()){
            TContractRentType tContractRentType = new TContractRentType();
@@ -100,6 +206,8 @@
    @Log(title = "合同管理-编辑合同", businessType =  BusinessType.UPDATE)
    @ApiOperation(value = "编辑合同")
    @PostMapping(value = "/updateContract")
    @PreAuthorize("@ss.hasPermi('system:contract:update')")
    public R<Boolean> updateContract(@Validated @RequestBody TContractDTO dto) {
        contractService.updateById(dto);
        contractRentTypeService.remove(new LambdaQueryWrapper<TContractRentType>()
@@ -118,6 +226,8 @@
    }
    @Log(title = "合同管理-批量删除合同", businessType = BusinessType.DELETE)
    @ApiOperation(value = "批量删除合同")
    @PreAuthorize("@ss.hasPermi('system:contract:delete')")
    @DeleteMapping(value = "/deleteContractByIds")
    public R<Boolean> deleteContractByIds
            (@RequestParam String ids) {
@@ -129,6 +239,8 @@
    @ApiOperation(value = "查询合同信息信息")
    @GetMapping(value = "/getContractById")
    @PreAuthorize("@ss.hasPermi('system:contract:detail')")
    public R<TContractVO> getContractById(@RequestParam String id) {
        TContractVO res = new TContractVO();
        TContract contract = contractService.getById(id);
@@ -158,32 +270,50 @@
            payMoney = payMoney.add(tBill.getPayFeesMoney()).add(tBill.getPayableFeesPenalty());
        }
        TCheckAcceptRecord tCheckAcceptRecord = checkAcceptRecordService.lambdaQuery().eq(TCheckAcceptRecord::getContractId, id).one();
        res.setCheckResult(tCheckAcceptRecord.getCheckResult());
        res.setCheckResult(Objects.nonNull(tCheckAcceptRecord)?tCheckAcceptRecord.getCheckResult():false);
        res.setPayMoney(payMoney);
        return R.ok(res);
    }
    @Log(title = "合同管理-撤销审批", businessType =  BusinessType.UPDATE)
    @ApiOperation(value = "撤销审批")
    @PreAuthorize("@ss.hasPermi('system:contract:revoke')")
    @GetMapping(value = "/updateContractStatus")
    public R<Boolean> updateContractStatus(String id) {
        TContract contract = contractService.getById(id);
        contract.setStatus("1");
        contractService.updateById(contract);
        return R.ok();
    }
    @PreAuthorize("@ss.hasPermi('system:contract:confirm')")
    @Log(title = "合同管理-确认结算", businessType =  BusinessType.UPDATE)
    @ApiOperation(value = "确认结算")
    @PostMapping(value = "/confirmSettlement")
    public R<Boolean> confirmSettlement(String id) {
        TContract contract = contractService.getById(id);
        contract.setStatus("8");
        contractService.updateById(contract);
        return R.ok();
    }
    @ApiOperation(value = "终止合同剩余未缴费账单列表")
    @PostMapping(value = "/contractBillList")
    @PreAuthorize("@ss.hasPermi('system:contract:billList')")
    public R<PageInfo<BillVO>> contractBillList(@RequestBody TContractBillQuery query) {
        return R.ok(contractService.contractBillList(query));
    }
    @ApiOperation(value = "终止合同")
    @PostMapping(value = "/terminateContract")
    @PreAuthorize("@ss.hasPermi('system:contract:terminate')")
    public R terminateContract(@RequestBody TerminateContractDTO dto) {
        contractService.terminateContract(dto);
        return R.ok();
    }
    @ApiOperation(value = "根据合同id查看验收记录")
    @GetMapping(value = "/getCheckByContractId")
    @PreAuthorize("@ss.hasPermi('system:contract:checkDetail')")
    public R<CheckAcceptRecordVO> getCheckByContractId(String id) {
        return R.ok(contractService.getCheckByContractId(id));
    }
@@ -191,6 +321,8 @@
    private WordUtil wordUtil;
    @ApiOperation(value = "生成合同附件")
    @PostMapping("/set")
    @Log(title = "生成合同附件", businessType = BusinessType.EXPORT)
    @PreAuthorize("@ss.hasPermi('system:contract:set')")
    public R<List<String>> set(@RequestBody SetContractDto dto,HttpServletResponse response){
        List<TContract> list = contractService.lambdaQuery().in(TContract::getId, dto.getIds()).list();
        List<String> res = new ArrayList<>();
@@ -228,6 +360,7 @@
     * 导出
     */
    @ApiOperation(value = "导出")
    @PreAuthorize("@ss.hasPermi('system:contract:export')")
    @Log(title = "导出", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void exportOpticalInspection(@RequestBody TContractQuery query)
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TFaultAreaDicController.java
@@ -53,7 +53,6 @@
        return R.ok(faultAreaDicService.pageList(query));
    }
    @PreAuthorize("@ss.hasPermi('system:faultArea:list')")
    @ApiOperation(value = "获取故障区域列表")
    @PostMapping(value = "/list")
    public R<List<TFaultAreaDic>> list() {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TFaultRepairMessageController.java
@@ -1,9 +1,22 @@
package com.ruoyi.web.controller.api;
import org.springframework.web.bind.annotation.RequestMapping;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.model.TFaultRepairMessage;
import com.ruoyi.system.query.TFaultRepairMessageQuery;
import com.ruoyi.system.service.TFaultRepairMessageService;
import com.ruoyi.system.vo.TFaultRepairMessageVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
 * <p>
@@ -13,9 +26,72 @@
 * @author xiaochen
 * @since 2025-01-17
 */
@Api(tags = "报修管理")
@RestController
@RequestMapping("/t-fault-repair-message")
public class TFaultRepairMessageController {
    private final TFaultRepairMessageService tFaultRepairMessageService;
    @Autowired
    public TFaultRepairMessageController(TFaultRepairMessageService tFaultRepairMessageService) {
        this.tFaultRepairMessageService = tFaultRepairMessageService;
    }
    /**
     * 获取报修管理列表
     */
    @PreAuthorize("@ss.hasPermi('system:fault:list')")
    @ApiOperation(value = "获取报修分页列表")
    @PostMapping(value = "/pageList")
    public R<PageInfo<TFaultRepairMessageVO>> pageList(@RequestBody TFaultRepairMessageQuery query) {
        return R.ok(tFaultRepairMessageService.pageList(query));
    }
    /**
     * 处理维修
     */
    @PreAuthorize("@ss.hasPermi('system:fault:update')")
    @Log(title = "报修信息-处理维修", businessType = BusinessType.UPDATE)
    @ApiOperation(value = "处理维修")
    @PostMapping(value = "/update")
    public R<Boolean> update(@Validated @RequestBody TFaultRepairMessage faultRepairMessage) {
        faultRepairMessage.setStatus(2);
        return R.ok(tFaultRepairMessageService.updateById(faultRepairMessage));
    }
    /**
     * 查看报修详情
     */
    @PreAuthorize("@ss.hasPermi('system:fault:detail')")
    @ApiOperation(value = "查看报修详情")
    @GetMapping(value = "/getDetailById")
    public R<TFaultRepairMessageVO> getDetailById(@RequestParam String id) {
        TFaultRepairMessageVO faultRepairMessageVO = tFaultRepairMessageService.getDetailById(id);
        return R.ok(faultRepairMessageVO);
    }
    /**
     * 删除报修
     */
    @PreAuthorize("@ss.hasPermi('system:fault:delete')")
    @Log(title = "报修信息-删除报修", businessType = BusinessType.DELETE)
    @ApiOperation(value = "删除报修")
    @DeleteMapping(value = "/deleteById")
    public R<Boolean> deleteById(@RequestParam String id) {
        return R.ok(tFaultRepairMessageService.removeById(id));
    }
    /**
     * 批量删除报修
     */
    @PreAuthorize("@ss.hasPermi('system:fault:delete')")
    @Log(title = "报修信息-删除报修", businessType = BusinessType.DELETE)
    @ApiOperation(value = "批量删除报修")
    @DeleteMapping(value = "/deleteByIds")
    public R<Boolean> deleteByIds(@RequestBody List<String> ids) {
        return R.ok(tFaultRepairMessageService.removeByIds(ids));
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TItemTypeController.java
@@ -54,7 +54,6 @@
    /**
     * 获取维修物品分类管理列表
     */
    @PreAuthorize("@ss.hasPermi('system:itemType:list')")
    @ApiOperation(value = "获取维修物品分类列表")
    @PostMapping(value = "/list")
    public R<List<TItemType>> list() {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java
@@ -54,7 +54,7 @@
                    //注入创建时间
                    if ("createTime".equals(field.getName())) {
                        field.setAccessible(true);
                        field.set(parameter, LocalDateTime.now());
//                        field.set(parameter, LocalDateTime.now());
                        field.setAccessible(false);
                    } 
                } catch (Exception e) {
@@ -77,7 +77,7 @@
                    }
                    if ("updateTime".equals(field.getName())) {
                        field.setAccessible(true);
                        field.set(parameter, LocalDateTime.now());
//                        field.set(parameter, new Date());
                        field.setAccessible(false);
                    }
                } catch (Exception e) {
ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java
@@ -82,105 +82,6 @@
//    }
    /**
     * 人员借用列表
     */
//    @ApiOperation(value = "人员借用列表")
//    @GetMapping("/userBorrowList")
//    public AjaxResult userBorrowList(@RequestParam(required = false) String name,
//                                     @RequestParam(required = false) Integer type)
//    {
//
//        UserAddListVO userAddListVO = new UserAddListVO();
//
//        Long companyId = tokenService.getLoginUser().getUser().getCompanyId();
//
//        List<TCompany> companyList = new ArrayList<>();
//        List<TDept> deptList = new ArrayList<>();
//        List<SysUser> userList = new ArrayList<>();
//        // 查询公司
//        if(Objects.nonNull(type) && type == 1){
//            companyList = companyService.userAddListByCompanyName(name);
//        }
//        // 查询部门
//        if(Objects.nonNull(type) && type == 2){
//            deptList = tDeptService.userAddListByDeptName(name);
//        }
//        // 查询用户
//        if(Objects.nonNull(type) && type == 3){
//            userList = userService.selectListByNamePhone(name);
//        }
//
//        if(Objects.isNull(type)){
//            companyList = companyService.userAddListByCompanyName(name);
//            deptList = tDeptService.userAddListByDeptName(name);
//            userList = userService.selectListByNamePhone(name);
//        }
//
//        List<Long> companyIds = companyList.stream().map(TCompany::getId).collect(Collectors.toList());
//        List<Long> deptCompanyIds = deptList.stream().map(TDept::getCompanyId).collect(Collectors.toList());
//        List<Long> userCompanyIds = userList.stream().map(SysUser::getCompanyId).collect(Collectors.toList());
//        companyIds.addAll(deptCompanyIds);
//        companyIds.addAll(userCompanyIds);
//
//        companyIds = companyIds.stream().distinct().collect(Collectors.toList());
//
//        if(CollectionUtils.isEmpty(companyIds)){
//            return AjaxResult.success(userAddListVO);
//        }
//        SysUser user1 = tokenService.getLoginUser().getUser();
//        if(!user1.isAdmin()){
//            companyIds = companyIds.stream().filter(e->!e.equals(companyId)).collect(Collectors.toList());
//        }
//
//        // 查询符合要求的公司
//        List<UserLevelVO> parent = companyService.userAddListByCompanyIds(companyIds);
//
//        List<TDept> depts = tDeptService.selectList();
//
//        List<SysUser> sysUsers = userService.selectList();
//
//        for (UserLevelVO userLevelVO : parent) {
//
//            // 找到公司下的部门
//            List<TDept> tDepts = depts.stream().filter(e -> userLevelVO.getKey().equals(e.getCompanyId())).collect(Collectors.toList());
//            List<UserLevelVO> children = new ArrayList<>();
//            // 封装部门
//            for (TDept dept : tDepts) {
//                userLevelVO.setChildren(children);
//                UserLevelVO userLevelVO1 = new UserLevelVO();
//                userLevelVO1.setKey(dept.getId());
//                userLevelVO1.setTitle(dept.getDeptName());
//                // 找到部门下的人员
//                List<SysUser> users;
//                if(StringUtils.isNotEmpty(name) && type == 3){
//                    users = sysUsers.stream().filter(e -> userLevelVO1.getKey().equals(e.getDeptId())
//                            && ((StringUtils.isNotEmpty(e.getNickName()) && e.getNickName().contains(name)))
//                            || (StringUtils.isNotEmpty(e.getPhonenumber()) && e.getPhonenumber().contains(name))).collect(Collectors.toList());
//                }else {
//                    users = sysUsers.stream().filter(e -> userLevelVO1.getKey().equals(e.getDeptId())).collect(Collectors.toList());
//                }
//                List<UserLevelVO> children1 = new ArrayList<>();
//                // 封装人员
//                for (SysUser user : users) {
//                    UserLevelVO userLevelVO2 = new UserLevelVO();
//                    userLevelVO2.setKey(user.getUserId());
//                    userLevelVO2.setTitle(user.getNickName());
//                    userLevelVO2.setAvatar(user.getAvatar());
//                    userLevelVO2.setFlag(true);
//                    children1.add(userLevelVO2);
//                }
//                userLevelVO1.setChildren(children1);
//
//                children.add(userLevelVO1);
//            }
//            userLevelVO.setChildren(children);
//        }
//        userAddListVO.setUserLevelVOS(parent);
//        userAddListVO.setUserList(sysUsers);
//        return AjaxResult.success(userAddListVO);
//    }
    /**
     * 获取用户详情
     */
    @ApiOperation(value = "获取用户详情")
@@ -245,37 +146,6 @@
        return AjaxResult.success();
    }
//    @Log(title = "用户管理", businessType = BusinessType.EXPORT)
////    // @PreAuthorize("@ss.hasPermi('system:user:export')")
//    @PostMapping("/export")
//    public void export(HttpServletResponse response, SysUser user)
//    {
//        List<SysUser> list = userService.selectUserList(user);
//        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
//        util.exportExcel(response, list, "用户数据");
//    }
//    @Log(title = "用户管理", businessType = BusinessType.IMPORT)
////    // @PreAuthorize("@ss.hasPermi('system:user:import')")
//    @PostMapping("/importData")
//    public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
//    {
//        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
//        List<SysUser> userList = util.importExcel(file.getInputStream());
//        String operName = getUsername();
//        String message = userService.importUser(userList, updateSupport, operName);
//        return AjaxResult.success(message);
//    }
//    @PostMapping("/importTemplate")
//    public void importTemplate(HttpServletResponse response)
//    {
//        ExcelUtil<SysUser> util = new ExcelUtil<SysUser>(SysUser.class);
//        util.importTemplateExcel(response, "用户数据");
//    }
    /**
     * 新增用户
     */
@@ -295,7 +165,7 @@
            return error("新增用户'" + user.getUserName() + "'失败,手机号码已存在");
        }
        user.setCreateBy(getUsername());
        user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
        user.setPassword(SecurityUtils.encryptPassword("123456"));
        userService.insertUser(user);
        return AjaxResult.success();
    }
ruoyi-admin/src/main/java/com/ruoyi/web/controller/task/TaskUtil.java
@@ -51,17 +51,16 @@
                long hours = ChronoUnit.HOURS.between(payableFeesTime, now);
                long l = hours / 72;
                if (l>0){
                    // 计算每天租金
                    long days = ChronoUnit.DAYS.between(tBill.getStartTime(),tBill.getEndTime());
                    BigDecimal everyDayMoney = tBill.getPayableFeesMoney().divide(new BigDecimal(days), 2, BigDecimal.ROUND_DOWN);
                    // 违约金比例
                    BigDecimal proportion = contract.getProportion();
                    // 预期x天后的违约金
                    BigDecimal money = everyDayMoney.multiply(proportion).multiply(new BigDecimal(l));
                    tBill.setPayableFeesPenalty(money);
                    // 应缴违约金
                    BigDecimal money = tBill.getOutstandingMoney().multiply(proportion);
                    TBill changeBill = new TBill();
                    changeBill.setId(tBill.getId());
                    changeBill.setPayableFeesPenalty(money);
                    billService.lockAndUpdateInfo(changeBill,2);
                }
            }
            billService.updateBatchById(list);
        } catch (Exception e) {
            e.printStackTrace();
@@ -69,497 +68,24 @@
    }
    // 每天凌晨00点执行的定时任务 用于合同生成第一笔账单
    @Scheduled(cron = "0 0 0 * * ?")
    public void dayOfFirstBill() {
        try {
            // 查询所有已签订的合同并且未生成第一笔账单的
            List<TContract> list = contractService.lambdaQuery().eq(TContract::getStatus, 4)
                    .isNull(TContract::getFirstPayTime).list();
            List<TBill> bills = new ArrayList<>();
            List<TContractRentType> contractRentTypes = contractRentTypeService.list();
            for (TContract contract : list) {
                contract.setFirstPayTime(contract.getStartTime().plusDays(10));
                // 第一次应缴费日期
                LocalDateTime firstPayTime = contract.getStartTime().plusDays(10).withHour(0).withMinute(0).withSecond(0);
                LocalDate localDate = contract.getStartTime().plusDays(10).toLocalDate();
                LocalDate now = LocalDate.now();
                // 如果应缴费日期和当前时间不相同 跳过
                if (!localDate.equals(now)) {
                    continue;
                }
                TBill rentBill = new TBill();
                rentBill.setContractId(contract.getId());
                rentBill.setContractNumber(contract.getContractNumber());
                LocalDateTime startPayTime = contract.getStartPayTime();
                LocalDateTime endTime1 = contract.getEndTime();
//                // 计算两个时间相差多少天
//                // 如果时间小于30天 需要计算每日租金
//                if (days<30){
//                    rentBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal("30"),2,BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
//                }else{
//                    rentBill.setPayableFeesMoney(contract.getPayType().equals("1")?contract.getMonthRent():
//                            contract.getPayType().equals("2")?contract.getMonthRent().multiply(new BigDecimal("3")):contract.getMonthRent().multiply(new BigDecimal("12")).setScale(2,BigDecimal.ROUND_DOWN));
//                }
                rentBill.setPayableFeesTime(firstPayTime);
                rentBill.setPayFeesStatus("1");
                rentBill.setBillType("1");
                rentBill.setStartTime(contract.getStartPayTime());
                TContractRentType tContractRentType = contractRentTypes.stream().filter(e -> e.getContractId().equals(contract.getId())).findFirst().orElse(null);
                if (tContractRentType!=null && contract.getStartPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(tContractRentType.getChangeTime())){
                    // 计算租金变动的天数
                    long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getStartPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12))+1L;
                    contract.setChangeTime(LocalDateTime.now());
                    // 递增递减的租金
                    BigDecimal contractRentTypeMoney = new BigDecimal("0");
                    // 不递增递减的租金
                    BigDecimal originalMoney = new BigDecimal("0");
                    // 原租金
                    switch (tContractRentType.getIncreasingDecreasingType()){
                        case 1:
                            switch (tContractRentType.getIncreasingDecreasing()){
                                case 1:
                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                    break;
                                case 2:
                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                    break;
                            }
                            break;
                        case 2:
                            switch (tContractRentType.getIncreasingDecreasing()){
                                case 1:
                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())));
                                    break;
                                case 2:
                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())));
                                    break;
                            }
                            break;
                    }
                    // 不需要涨租金的时间段
                    long originalDays = ChronoUnit.DAYS.between(contract.getFirstPayTime(), tContractRentType.getChangeTime());
                    originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                            .multiply(new BigDecimal(originalDays));
                    rentBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                    if (contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(contract.getEndTime())){
                        rentBill.setEndTime(contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
                    }else{
                        rentBill.setEndTime(contract.getEndTime());
                    }
                }else{
                    if (contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(contract.getEndTime())){
                        rentBill.setEndTime(contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
                    }else{
                        rentBill.setEndTime(contract.getEndTime());
                    }
                    // 不走递增递减
                    long allDays = ChronoUnit.DAYS.between(contract.getFirstPayTime(), rentBill.getEndTime());
                    rentBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
                }
                // 租金账单
                bills.add(rentBill);
                // 押金账单
                TBill depositBill = new TBill();
                depositBill.setContractId(contract.getId());
                depositBill.setContractNumber(contract.getContractNumber());
                depositBill.setPayableFeesMoney(contract.getDeposit());
                depositBill.setPayableFeesTime(firstPayTime);
                depositBill.setPayFeesStatus("1");
                depositBill.setBillType("2");
                bills.add(depositBill);
            }
            contractService.updateBatchById(list);
            billService.saveBatch(bills);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 每天凌晨00点执行的定时任务 用于生成合同期最后一笔账单
    // 每天凌晨00点执行的定时任务 根据应缴费日期修改账单状态
    @Scheduled(cron = "0 0 0 * * ?")
    public void dayOfEndBill() {
        try {
            // 查询所有已签订的合同并且已经生成第一笔账单的
            List<TContract> list = contractService.lambdaQuery().eq(TContract::getStatus, 4)
                    .ge(TContract::getEndTime,LocalDateTime.now())
                    .isNotNull(TContract::getFirstPayTime).list();
            List<TContractRentType> contractRentTypes = contractRentTypeService.list();
            List<TBill> bills = new ArrayList<>();
            for (TContract contract : list) {
                TBill beforeBill = billService.lambdaQuery().eq(TBill::getContractId, contract.getId()).eq(TBill::getBillType, 1).orderByDesc(TBill::getCreateTime)
                        .last("limit 1").one();
                if (!(beforeBill.getEndTime().toLocalDate().equals(contract.getEndTime().toLocalDate()))
                    &&
                        (contract.getPayType().equals("1")?
                        beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                        beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12))
                        .with(TemporalAdjusters.lastDayOfMonth()).isAfter(contract.getEndTime())
                && beforeBill.getEndTime().isBefore(contract.getEndTime())
                ){
                    TBill tBill = new TBill();
                    tBill.setContractId(contract.getId());
                    long days = ChronoUnit.DAYS.between((contract.getPayType().equals("1")?
                            beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                            beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12)).with(TemporalAdjusters.firstDayOfMonth()), contract.getEndTime())+1L;
                    if (contract.getIsIncreasing()){
                        TContractRentType tContractRentType = contractRentTypes.stream().filter(e -> e.getContractId().equals(contract.getId())).findFirst().orElse(null);
                        if (tContractRentType!=null
                                && beforeBill.getEndTime().isBefore(tContractRentType.getChangeTime())
                                && beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).with(TemporalAdjusters.lastDayOfMonth()).isAfter(tContractRentType.getChangeTime())){
                            // 如果没变过
                            if (contract.getChangeTime()==null){
                                contract.setChangeTime(LocalDateTime.now());
                                // 租金递增递减的时长 天
                                long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                // 递增递减的租金
                                BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                // 不递增递减的租金
                                BigDecimal originalMoney = new BigDecimal("0");
                                // 原租金
                                switch (tContractRentType.getIncreasingDecreasingType()){
                                    case 1:
                                        switch (tContractRentType.getIncreasingDecreasing()){
                                            case 1:
                                                contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                break;
                                            case 2:
                                                contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                break;
                                        }
                                        break;
                                    case 2:
                                        switch (tContractRentType.getIncreasingDecreasing()){
                                            case 1:
                                                contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())));
                                                break;
                                            case 2:
                                                contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())));
                                                break;
                                        }
                                        break;
                                }
                                // 不需要涨租金的时间段
                                long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                        .multiply(new BigDecimal(originalDays));
                                tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                            }else{
                                // 之前已经涨、跌过租金了 需要判断周期是否还需要再涨、跌
                                if ((LocalDateTime.now().getYear() - contract.getChangeTime().getYear())%tContractRentType.getCycleTime()==0){
                                    contract.setChangeTime(LocalDateTime.now());
                                    // 租金递增递减的时长 天
                                    long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                    // 递增递减的租金
                                    BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                    // 不递增递减的租金
                                    BigDecimal originalMoney = new BigDecimal("0");
                                    // 原租金
                                    switch (tContractRentType.getIncreasingDecreasingType()){
                                        case 1:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                    break;
                                            }
                                            break;
                                        case 2:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())));
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())));
                                                    break;
                                            }
                                            break;
                                    }
                                    // 不需要涨租金的时间段
                                    long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                    originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                            .multiply(new BigDecimal(originalDays));
                                    tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                }else{
                                    // 不涨租金 用上次的
                                    // 租金递增递减的时长 天
                                    long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                    // 递增递减的租金
                                    BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                    // 不递增递减的租金
                                    BigDecimal originalMoney = new BigDecimal("0");
                                    // 原租金
                                    switch (tContractRentType.getIncreasingDecreasingType()){
                                        case 1:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days))) ;
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                    break;
                                            }
                                            break;
                                        case 2:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent()).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent()).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    break;
                                            }
                                            break;
                                    }
                                    // 不需要涨租金的时间段
                                    long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                    originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                            .multiply(new BigDecimal(originalDays));
                                    tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                }
                            }
                        }
                    }else{
                        long allDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), contract.getEndTime());
                        tBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
                    }
                    tBill.setContractNumber(contract.getContractNumber());
                    if (beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).getDayOfMonth()<=15){
                        tBill.setPayableFeesTime(contract.getEndTime().withHour(0).withMinute(0).withSecond(0));
                    }else{
                        tBill.setPayableFeesTime((contract.getPayType().equals("1")?
                                beforeBill.getEndTime().plusMonths(1).withDayOfMonth(15):contract.getPayType().equals("2")?
                                beforeBill.getEndTime().plusMonths(3).withDayOfMonth(15):beforeBill.getEndTime().withDayOfMonth(15).plusMonths(12).withHour(0).withMinute(0).withSecond(0)));
                    }
            List<TBill> list = billService.lambdaQuery().eq(TBill::getPayFeesStatus, "2").list();
            for (TBill tBill : list) {
                if (tBill.getPayableFeesTime().toLocalDate().equals(LocalDate.now())){
                    tBill.setPayFeesStatus("1");
                    tBill.setBillType("1");
                    tBill.setStartTime(beforeBill.getEndTime().plusDays(1));
                    tBill.setEndTime(beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
                    bills.add(tBill);
                }
            }
            billService.saveBatch(bills);
            billService.updateBatchById(list);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    // 每月15号凌晨执行的定时任务 用于生成租金账单
    @Scheduled(cron = "0 0 0 15 * ?")
    public void monthOfBill() {
        try {
            // 查询所有已签订的合同
            List<TContract> list = contractService.lambdaQuery().eq(TContract::getStatus, 4)
                    .isNotNull(TContract::getFirstPayTime)
                    .ge(TContract::getEndTime, LocalDateTime.now())
                    .list();
            List<TContractRentType> contractRentTypes = contractRentTypeService.list();
            List<TBill> bills = new ArrayList<>();
            for (TContract contract : list) {
                TBill beforeBill = billService.lambdaQuery().eq(TBill::getContractId, contract.getId()).eq(TBill::getBillType, 1).orderByDesc(TBill::getCreateTime)
                        .last("limit 1").one();
                if (beforeBill.getEndTime().toLocalDate().equals(contract.getEndTime().toLocalDate()))continue;
                if (beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).with(TemporalAdjusters.lastDayOfMonth()).isBefore(contract.getEndTime())){
                    TBill tBill = new TBill();
                    tBill.setContractId(contract.getId());
                    tBill.setContractNumber(contract.getContractNumber());
                    // 根据支付方式判断需不需要生成订单
                    if (!(beforeBill.getEndTime().toLocalDate().equals(contract.getEndTime().toLocalDate()))
                            &&
                            (contract.getPayType().equals("1")?
                                    beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                                    beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12))
                                    .with(TemporalAdjusters.lastDayOfMonth()).isBefore(contract.getEndTime())
                            && beforeBill.getEndTime().isBefore(contract.getEndTime())
                    ){
                        tBill.setContractId(contract.getId());
                        long days = ChronoUnit.DAYS.between(beforeBill.getEndTime(), (contract.getPayType().equals("1")?
                                beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                                beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12)).with(TemporalAdjusters.lastDayOfMonth()))+1L;
                        if (contract.getIsIncreasing()){
                            TContractRentType tContractRentType = contractRentTypes.stream().filter(e -> e.getContractId().equals(contract.getId())).findFirst().orElse(null);
                            if (tContractRentType!=null
                                    && beforeBill.getEndTime().isBefore(tContractRentType.getChangeTime())
                                    && beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).with(TemporalAdjusters.lastDayOfMonth()).isAfter(tContractRentType.getChangeTime())){
                                // 如果没变过
                                if (contract.getChangeTime()==null){
                                    contract.setChangeTime(LocalDateTime.now());
                                    // 租金递增递减的时长 天
                                    long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                    // 递增递减的租金
                                    BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                    // 不递增递减的租金
                                    BigDecimal originalMoney = new BigDecimal("0");
                                    // 原租金
                                    switch (tContractRentType.getIncreasingDecreasingType()){
                                        case 1:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                    break;
                                            }
                                            break;
                                        case 2:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())));
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())));
                                                    break;
                                            }
                                            break;
                                    }
                                    // 不需要涨租金的时间段
                                    long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                    originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                            .multiply(new BigDecimal(originalDays));
                                    tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                }else{
                                    // 之前已经涨、跌过租金了 需要判断周期是否还需要再涨、跌
                                    if ((LocalDateTime.now().getYear() - contract.getChangeTime().getYear())%tContractRentType.getCycleTime()==0){
                                        contract.setChangeTime(LocalDateTime.now());
                                        // 租金递增递减的时长 天
                                        long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                        // 递增递减的租金
                                        BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                        // 不递增递减的租金
                                        BigDecimal originalMoney = new BigDecimal("0");
                                        // 原租金
                                        switch (tContractRentType.getIncreasingDecreasingType()){
                                            case 1:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                        contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN)).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                        contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).subtract(tContractRentType.getNumericalValue()).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN))));
                                                        break;
                                                }
                                                break;
                                            case 2:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())));
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        contract.setChangeRent(contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())));
                                                        break;
                                                }
                                                break;
                                        }
                                        // 不需要涨租金的时间段
                                        long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                        originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                                .multiply(new BigDecimal(originalDays));
                                        tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                    }else{
                                        // 不涨租金 用上次的
                                        // 租金递增递减的时长 天
                                        long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                        // 递增递减的租金
                                        BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                        // 不递增递减的租金
                                        BigDecimal originalMoney = new BigDecimal("0");
                                        // 原租金
                                        switch (tContractRentType.getIncreasingDecreasingType()){
                                            case 1:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days))) ;
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(days)));
                                                        break;
                                                }
                                                break;
                                            case 2:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent()).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent()).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        break;
                                                }
                                                break;
                                        }
                                        // 不需要涨租金的时间段
                                        long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                        originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                                .multiply(new BigDecimal(originalDays));
                                        tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                    }
                                }
                            }
                        }else{
                            long allDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), (contract.getPayType().equals("1")?
                                    beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                                    beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12)).with(TemporalAdjusters.lastDayOfMonth()));
                            tBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
                        }
                        tBill.setContractNumber(contract.getContractNumber());
                        if (beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).getDayOfMonth()<=15){
                            tBill.setPayableFeesTime(contract.getEndTime());
                        }else{
                            tBill.setPayableFeesTime((contract.getPayType().equals("1")?
                                    beforeBill.getEndTime().plusMonths(1).withDayOfMonth(15):contract.getPayType().equals("2")?
                                    beforeBill.getEndTime().plusMonths(3).withDayOfMonth(15):beforeBill.getEndTime().withDayOfMonth(15).plusMonths(12)));
                        }
                        tBill.setPayFeesStatus("1");
                        tBill.setBillType("1");
                        tBill.setStartTime(beforeBill.getEndTime().plusDays(1));
                        tBill.setEndTime(contract.getEndTime());
                        bills.add(tBill);
                    }
                    tBill.setPayableFeesMoney(contract.getMonthRent().multiply(new BigDecimal("3")));
                    tBill.setPayableFeesTime(LocalDateTime.now().withHour(0).withMinute(0).withSecond(0));
                    tBill.setPayFeesStatus("1");
                    tBill.setBillType("1");
                    tBill.setStartTime(beforeBill.getEndTime().plusMonths(1).with(TemporalAdjusters.firstDayOfMonth()));
                    tBill.setEndTime(beforeBill.getEndTime().plusMonths(1).with(TemporalAdjusters.lastDayOfMonth()));
                    bills.add(tBill);
                }
            }
            billService.saveBatch(bills);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
ruoyi-admin/src/main/resources/application-test.yml
@@ -193,9 +193,9 @@
    accessPath: /file/
    allowExt: .jpg|.png|.gif|.jpeg|.doc|.docx|.apk|.MP4|.mp4|.pdf|.PDF
wx:
  config:
    appId: wxc3985a05da7d86dc
    secret: 5cca42633c25439613b328c08ef20cc9
  conf:
    appId: wxe91f1af7638aa5dd
    secretId: a787e1a462715604e0c9528b6d8960d1
#OSS及短信配置
code:
  config:
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/COSController.java
New file
@@ -0,0 +1,55 @@
package com.ruoyi.web.controller.api;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.web.controller.tool.TencentCosUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
/**
 * @author HJL
 * @version 1.0
 * @since 2024-05-30 9:27
 */
@RestController
@RequestMapping("/cos")
@Api(tags = "公共-文件上传")
public class COSController {
    @Resource
    private TencentCosUtil tencentCosUtil;
    @PostMapping("/upload")
    @ApiOperation(value = "文件上传", tags = "公共-文件上传")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "文件", name = "file", dataType = "MultipartFile", required = true)
    })
    public R<String> upload(@RequestParam("file") MultipartFile file) {
        String url = tencentCosUtil.upLoadFile(file);
        return R.ok(url, url);
    }
    @PostMapping("/downloadImg")
    @ApiOperation(value = "文件下载", tags = "公共-文件下载")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "文件url", name = "url", dataType = "String", required = true)
    })
    public String downloadImg(@RequestParam("url") String url) {
       return tencentCosUtil.downLoadFileImg(url);
    }
    @PostMapping("/download")
    @ApiOperation(value = "文件下载", tags = "公共-文件下载")
    @ApiImplicitParams({
            @ApiImplicitParam(value = "文件url", name = "url", dataType = "String", required = true)
    })
    public void download(@RequestParam("url") String url) {
        tencentCosUtil.downLoadFile(url);
    }
}
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TBillController.java
@@ -55,9 +55,8 @@
    @ApiOperation(value = "缴费账单查询分页列表")
    @PostMapping("list")
    public R<PageInfo<TBillDto>> list(@RequestBody TBillQuery query){
        if (StringUtils.isEmpty(query.getUserId())){
            throw new ServiceException("用户ID不能为空");
        }
        String userId = tokenService.getLoginUserApplet().getUserId();
        query.setUserId(userId);
        PageInfo<TBillDto> pageInfo = tBillService.queryPage(query);
        return R.ok(pageInfo);
    }
@@ -65,9 +64,8 @@
    @ApiOperation(value = "缴费账单查询列表")
    @PostMapping("/getBillIds")
    public R<List<String>> getBillIds(@RequestBody TBillQuery query){
        if (StringUtils.isEmpty(query.getUserId())){
            throw new ServiceException("用户ID不能为空");
        }
        String userId = tokenService.getLoginUserApplet().getUserId();
        query.setUserId(userId);
        List<String> billIds = tBillService.getBillIds(query);
        return R.ok(billIds);
    }
@@ -90,9 +88,14 @@
            billVO.setHouse(houseService.getById(contract.getHouseId()));
            billVO.setMonthRent(contract.getMonthRent());
            billVO.setPayType(contract.getPayType());
            billVO.setDeposit(contract.getDeposit());
            billVO.setPartyOnePerson(contract.getPartyOnePerson());
            billVO.setPartyOnePhone(contract.getPartyOnePhone());
            billVO.setConcatStartTime(contract.getStartTime());
            billVO.setConcatEndTime(contract.getEndTime());
        });
        billVO.setBillType(DictUtils.getDictLabel(DictConstants.DICT_TYPE_BILL_TYPE,billVO.getBillType()));
        billVO.setPayFeesStatus(DictUtils.getDictLabel(DictConstants.DICT_TYPE_PAY_FEES_STATUS,billVO.getPayFeesStatus()));
//        billVO.setPayFeesStatus(DictUtils.getDictLabel(DictConstants.DICT_TYPE_PAY_FEES_STATUS,billVO.getPayFeesStatus()));
        return R.ok(billVO);
    }
@@ -119,8 +122,7 @@
    @PostMapping(value = "/invoiceList")
    public R<PageInfo<TBillDto>> invoiceList(@RequestBody TBillQuery query) {
//        Long userId = tokenService.getLoginUser().getUserId();
        String userId = "1881967035070177281";
        String userId = tokenService.getLoginUserApplet().getUserId();
        query.setUserId(userId);
        PageInfo<TBillDto> pageInfo = tBillService.invoiceList(query);
        return R.ok(pageInfo);
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TFaultRepairMessageController.java
@@ -1,9 +1,26 @@
package com.ruoyi.web.controller.api;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.dto.TFaultRepairMessageDTO;
import com.ruoyi.system.model.*;
import com.ruoyi.system.service.*;
import com.ruoyi.system.vo.TFaultAreaDicVO;
import com.ruoyi.system.vo.TItemTypeVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.stream.Collectors;
/**
 * <p>
@@ -13,9 +30,94 @@
 * @author xiaochen
 * @since 2025-01-17
 */
@Api(tags = "报修管理")
@RestController
@RequestMapping("/t-fault-repair-message")
public class TFaultRepairMessageController {
    private final TFaultRepairMessageService tFaultRepairMessageService;
    private final TItemService itemService;
    private final TItemTypeService itemTypeService;
    private final TFaultAreaDicService faultAreaDicService;
    private final TFaultDescribeDicService faultDescribeDicService;
    private final TokenService tokenService;
    private final TContractService contractService;
    private final THouseService houseService;
    @Autowired
    public TFaultRepairMessageController(TFaultRepairMessageService tFaultRepairMessageService, TItemService itemService, TItemTypeService itemTypeService, TFaultAreaDicService faultAreaDicService, TFaultDescribeDicService faultDescribeDicService, TokenService tokenService, TContractService contractService, THouseService houseService) {
        this.tFaultRepairMessageService = tFaultRepairMessageService;
        this.itemService = itemService;
        this.itemTypeService = itemTypeService;
        this.faultAreaDicService = faultAreaDicService;
        this.faultDescribeDicService = faultDescribeDicService;
        this.tokenService = tokenService;
        this.contractService = contractService;
        this.houseService = houseService;
    }
    /**
     * 获取维修物品二级结构
     */
    @ApiOperation(value = "获取维修物品二级结构")
    @PostMapping(value = "/getItemList")
    public R<List<TItemTypeVO>> getItemList() {
        List<TItemTypeVO> itemTypes = itemTypeService.getItemList();
        List<TItem> items = itemService.list();
        itemTypes.forEach(itemType -> {
            itemType.setItemList(items.stream().filter(item -> itemType.getId().equals(item.getTypeId())).collect(Collectors.toList()));
        });
        return R.ok(itemTypes);
    }
    /**
     * 获取故障区域列表
     */
    @ApiOperation(value = "获取故障区域二级结构")
    @PostMapping(value = "/getAreaDicList")
    public R<List<TFaultAreaDicVO>> getAreaDicList() {
        List<TFaultAreaDicVO> faultAreaDicVOS = faultAreaDicService.getAreaDicList();
        List<TFaultDescribeDic> faultDescribeDicList = faultDescribeDicService.list();
        faultAreaDicVOS.forEach(areaDicVO -> {
            areaDicVO.setFaultDescribeDicList(faultDescribeDicList.stream().filter(item -> areaDicVO.getId().equals(item.getFaultId())).collect(Collectors.toList()));
        });
        return R.ok(faultAreaDicVOS);
    }
    /**
     * 通过当前租户查询合同房源信息
     */
    @ApiOperation(value = "通过当前租户查询合同房源信息")
    @PostMapping(value = "/getConcatByTenantId")
    public R<List<TContract>> getConcatByTenantId() {
//        Long userId = tokenService.getLoginUser().getUserId();
        String tenantId = "1881967035070177281";
        // 查询合同信息
        List<TContract> list = contractService.list(Wrappers.lambdaQuery(TContract.class)
                .eq(TContract::getTenantId, tenantId)
                .eq(TContract::getStatus, 4));
        if(CollectionUtils.isEmpty(list)){
            return R.ok(list);
        }
        List<String> houseIds = list.stream().map(TContract::getHouseId).collect(Collectors.toList());
        List<THouse> houseList = houseService.list(Wrappers.lambdaQuery(THouse.class)
                .in(THouse::getId, houseIds));
        list.forEach(item -> {
            item.setHouseName(houseList.stream().filter(house -> house.getId().equals(item.getHouseId())).findFirst().orElse(new THouse()).getHouseName());
        });
        return R.ok(list);
    }
    /**
     * 添加报修信息
     */
    @ApiOperation(value = "添加报修信息")
    @PostMapping(value = "/addFault")
    public R<String> addFault(@RequestBody TFaultRepairMessageDTO dto) {
        tFaultRepairMessageService.save(dto);
        return R.ok();
    }
}
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/TInformationController.java
@@ -2,19 +2,16 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.system.model.TInformation;
import com.ruoyi.system.query.TInformationQuery;
import com.ruoyi.system.service.TInformationService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
/**
 * <p>
@@ -30,9 +27,11 @@
public class TInformationController {
    private final TInformationService informationService;
    private final RedisCache redisCache;
    @Autowired
    public TInformationController(TInformationService informationService) {
    public TInformationController(TInformationService informationService, RedisCache redisCache) {
        this.informationService = informationService;
        this.redisCache = redisCache;
    }
    /**
@@ -44,5 +43,16 @@
        return R.ok(informationService.pageList(query));
    }
    /**
     * 获取资讯管理详情
     */
    @ApiOperation(value = "获取资讯管理详情")
    @GetMapping(value = "/getDetailById")
    public R<TInformation> getDetailById(@RequestParam String id) {
        // 处理查看次数
        redisCache.increment(Constants.INFORMATION_VIEW + id);
        return R.ok(informationService.getById(id));
    }
}
ruoyi-applet/src/main/java/com/ruoyi/web/controller/api/WxLoginController.java
New file
@@ -0,0 +1,255 @@
package com.ruoyi.web.controller.api;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.entity.SysMenu;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.entity.TTenantResp;
import com.ruoyi.common.core.domain.model.LoginBody;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.domain.model.LoginUserApplet;
import com.ruoyi.common.core.utils.HttpUtils;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.framework.web.service.SysLoginService;
import com.ruoyi.framework.web.service.TokenService;
import com.ruoyi.system.model.TTenant;
import com.ruoyi.system.service.TTenantService;
import com.ruoyi.system.utils.wx.body.resp.Code2SessionRespBody;
import com.ruoyi.system.utils.wx.body.resq.Code2SessionResqBody;
import com.ruoyi.system.utils.wx.model.WeixinProperties;
import com.ruoyi.system.utils.wx.pojo.AppletUserDecodeData;
import com.ruoyi.system.utils.wx.pojo.AppletUserEncrypteData;
import com.ruoyi.system.utils.wx.tools.WxAppletTools;
import com.ruoyi.system.utils.wx.tools.WxUtils;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * <p>
 *  微信小程序登录 前端控制器
 * </p>
 *
 * @author xiaochen
 * @since 2024-08-06
 */
@Slf4j
@RestController
@RequestMapping("/wxLogin")
public class WxLoginController {
    @Autowired
    private WeixinProperties wxConfig;
    @Autowired
    private RestTemplate wxRestTemplate;
    @Resource
    private RedisService redisService;
    @Resource
    private TTenantService tTenantService;
    /**
     * 上传文件存储在本地的根路径
     */
//    @Value("${file.upload.location}")
//    private String localFilePath;
    @Resource
    private TokenService tokenService;
    @Autowired
    private SysLoginService loginService;
    /**
     * 账号密码登录
     *
     * @param loginBody 登录信息
     * @return 结果
     */
    @ApiOperation(value = "账号密码登录",notes = "管理员账号密码登录")
    @PostMapping("/login")
    public AjaxResult login(@RequestBody LoginBody loginBody)
    {
        AjaxResult ajax = AjaxResult.success();
        // 生成令牌
        LoginUser loginUser = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
                loginBody.getUuid());
        ajax.put(Constants.TOKEN, tokenService.createToken(loginUser));
        List<SysRole> roles = loginUser.getUser().getRoles();
        if(CollectionUtils.isEmpty(roles)){
            return AjaxResult.error("请关联角色!");
        }
        if(roles.get(0).getStatus() == 1){
            return AjaxResult.error("该账号角色已被禁用!");
        }
        return ajax;
    }
    @ApiOperation(value = "通过code获得openid,获取用户信息",tags = {"微信小程序登录"})
    @PostMapping("/openIdByJsCode")
    public R<Map<String, Object>> openIdByJsCode(@RequestBody AppletUserEncrypteData data) {
        log.info("<<<<<<<<换取openid开始<<<<<<<<:{}", data.getCode());
        WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, wxConfig, redisService);
        Code2SessionRespBody body = appletTools.getOpenIdByJscode2session(new Code2SessionResqBody().build(data.getCode()));
        String openid = body.getOpenid();
        String sessionKey = body.getSessionKey();
        // 用户信息解密 数据验签
//        if (StringUtils.isNotBlank(data.getSignature())) {
//            WxUtils.verifySignature(data.getRawData(), sessionKey, data.getSignature());
//        }
        if(StringUtils.isEmpty(data.getEncryptedData()) || StringUtils.isEmpty(data.getIv())){
            return R.fail("已拒绝授权");
        }
        AppletUserDecodeData appletUserDecodeData = WxUtils.encryptedData(data.getEncryptedData(), sessionKey,  data.getIv());
        appletUserDecodeData.setOpenId(openid);
        // 先使用openId和当前手机号进行查询
        TTenant tenant = tTenantService.getOne(Wrappers.lambdaQuery(TTenant.class)
                .eq(TTenant::getOpenId, appletUserDecodeData.getOpenId())
                .eq(TTenant::getPhone, appletUserDecodeData.getPhoneNumber()));
        if (tenant==null){
//            appUser.setTenantAttributes();
//            appUser.setTenantType();
            tenant = new TTenant();
            tenant.setPhone(appletUserDecodeData.getPhoneNumber());
            tenant.setAccount(appletUserDecodeData.getPhoneNumber());
            tenant.setPassword(SecurityUtils.encryptPassword(appletUserDecodeData.getPhoneNumber().substring(5)));
            tenant.setOpenId(appletUserDecodeData.getOpenId());
            // 手机号中间四位替换为*
            tenant.setResidentName(appletUserDecodeData.getPhoneNumber().replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2"));
            tTenantService.save(tenant);
        }
        LoginUserApplet loginUserApplet = new LoginUserApplet();
        TTenantResp tTenantResp = new TTenantResp();
        BeanUtils.copyProperties(tenant, tTenantResp);
        loginUserApplet.setUser(tTenantResp);
        loginUserApplet.setUserId(tenant.getId());
        Map<String, Object> tokenInfos = new HashMap<>();
        tokenInfos.put("token",tokenService.createTokenApplet(loginUserApplet));
        tokenInfos.put("info",loginUserApplet);
        return R.ok(tokenInfos);
    }
//    @ApiOperation(value = "获取微信小程序二维码",tags = {"获取微信小程序二维码"})
//    @PostMapping("/getQRCode")
//    public AjaxResult getQRCode() {
//        InputStream inputStream = null;
//        OutputStream outputStream = null;
//        WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, wxConfig, redisService);
//        String accessToken = appletTools.getAccessToken("");
//        try {
//            String url = "https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=" + accessToken;
//            Map<String, Object> param = new HashMap<>();
////            param.put("page", "pageA/houseDetail");
//            param.put("check_path", false);
//            param.put("env_version", "trial");
//            param.put("width", 200); //二维码尺寸
//            param.put("is_hyaline", true); // 是否需要透明底色, is_hyaline 为true时,生成透明底色的小程序码 参数仅对小程序码生效
//            param.put("auto_color", true); // 自动配置线条颜色,如果颜色依然是黑色,则说明不建议配置主色调 参数仅对小程序码生效
//            Map<String, Object> line_color = new HashMap<>();
//            line_color.put("r", 0);
//            line_color.put("g", 0);
//            line_color.put("b", 0);
//            param.put("line_color", line_color);
//            System.err.println("调用生成微信URL接口传参:" + param);
//            MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
//            HttpEntity requestEntity = new HttpEntity(param, headers);
//            ResponseEntity<byte[]> entity = wxRestTemplate.exchange(url, HttpMethod.POST, requestEntity, byte[].class, new Object[0]);
//            System.err.println("调用小程序生成微信永久小程序码URL接口返回结果:" + entity.getBody());
//            byte[] result = entity.getBody();
//            System.err.println(Base64.encodeBase64String(result));
//            inputStream = new ByteArrayInputStream(result);
//            String finalFileName = System.currentTimeMillis() + "" + new SecureRandom().nextInt(0x0400) + ".jpeg";
////            MultipartFile multipartFile = convertInputStreamToMultipartFile(inputStream, finalFileName, "image/jpeg");
////            String name = FileUploadUtils.upload(localFilePath, multipartFile);
////            System.err.println(name);
//            return AjaxResult.success(null);
//        } catch (Exception e) {
//            System.err.println("调用小程序生成微信永久小程序码URL接口异常" + e);
//        } finally {
//            if (inputStream != null) {
//                try {
//                    inputStream.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//            if (outputStream != null) {
//                try {
//                    outputStream.close();
//                } catch (IOException e) {
//                    e.printStackTrace();
//                }
//            }
//        }
//        return AjaxResult.success();
//    }
    /**
     * 获取微信token
     * @return
     */
    @PostMapping("/getWXToken")
    public R<String> getWXToken(){
        WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, wxConfig, redisService);
        String accessToken = appletTools.getAccessToken("");
        return R.ok(accessToken);
    }
    /**
     * 敏感词检测
     * @param content
     * @param openid
     * @return
     */
    @PostMapping("/sensitiveWordDetection")
    public R<Boolean> sensitiveWordDetection (@RequestParam("content") String content, @RequestParam("openid") String openid){
        WxAppletTools appletTools = new WxAppletTools(wxRestTemplate, wxConfig, redisService);
        String accessToken = appletTools.getAccessToken("");
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("content", content);
        jsonObject.put("version", 2);
        jsonObject.put("scene", 2);
        jsonObject.put("openid", openid);
        String post = HttpUtils.post("https://api.weixin.qq.com/wxa/msg_sec_check?access_token=" + accessToken, jsonObject.toString());
        JSONObject object = JSONObject.parseObject(post);
        Integer errcode = object.getInteger("errcode");
        if(0 != errcode){
            throw new RuntimeException(object.getString("errmsg"));
        }
        JSONArray detail = object.getJSONArray("detail");
        for (int i = 0; i < detail.size(); i++) {
            JSONObject jsonObject1 = detail.getJSONObject(i);
            Integer errcode1 = jsonObject1.getInteger("errcode");
            if(0 == errcode1){
                String suggest = jsonObject1.getString("suggest");
                Integer label = jsonObject1.getInteger("label");
                String keyword = jsonObject1.getString("keyword");
                Integer prob = jsonObject1.getInteger("prob");
                if(("risky".equals(suggest) || "review".equals(suggest)) && 100 != label && com.ruoyi.common.utils.StringUtils.isNotEmpty(keyword) && 80 <= prob){
                    return R.ok(true);
                }
            }
        }
        return R.ok(false);
    }
}
ruoyi-applet/src/main/java/com/ruoyi/web/controller/interceptor/MybatisInterceptor.java
@@ -56,7 +56,7 @@
                    //注入创建时间
                    if ("createTime".equals(field.getName())) {
                        field.setAccessible(true);
                        field.set(parameter, new Date());
//                        field.set(parameter, new Date());
                        field.setAccessible(false);
                    } 
                } catch (Exception e) {
@@ -79,7 +79,7 @@
                    }
                    if ("updateTime".equals(field.getName())) {
                        field.setAccessible(true);
                        field.set(parameter, new Date());
//                        field.set(parameter, new Date());
                        field.setAccessible(false);
                    }
                } catch (Exception e) {
ruoyi-applet/src/main/java/com/ruoyi/web/controller/tool/TencentCosUtil.java
New file
@@ -0,0 +1,187 @@
package com.ruoyi.web.controller.tool;
import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;
import com.ruoyi.common.utils.WebUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Base64;
import java.util.UUID;
/**
 * @author HJL
 */
@Component
public class TencentCosUtil {
    /**
     * COS的SecretId
     */
    @Value("${cos.client.accessKey}")
    private String secretId;
    /**
     * COS的SecretKey
     */
    @Value("${cos.client.secretKey}")
    private String secretKey;
    /**
     * 文件上传后访问路径的根路径,后面要最佳文件名字与类型
     */
    @Value("${cos.client.rootSrc}")
    private String rootSrc;
    /**
     * 上传的存储桶的地域
     */
    @Value("${cos.client.bucketAddr}")
    private String bucketAddr;
    /**
     * 存储桶的名字,是自己在存储空间自己创建的,我创建的名字是:qq-test-1303******
     */
    @Value("${cos.client.bucket}")
    private String bucketName;
    /**
     * 文件存放位置
     */
    @Value("${cos.client.location}")
    private String location;
    /**
     * 1.调用静态方法getCosClient()就会获得COSClient实例
     * 2.本方法根据永久密钥初始化 COSClient的,官方是不推荐,官方推荐使用临时密钥,是可以限制密钥使用权限,创建cred时有些区别
     *
     * @return COSClient实例
     */
    private COSClient getCosClient() {
        // 1 初始化用户身份信息(secretId, secretKey)。
        COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
        // 2.1 设置存储桶的地域(上文获得)
        Region region = new Region(bucketAddr);
        ClientConfig clientConfig = new ClientConfig(region);
        // 2.2 使用https协议传输
        clientConfig.setHttpProtocol(HttpProtocol.https);
        // 生成 cos 客户端
        return new COSClient(cred, clientConfig);
    }
    /**
     * 只要调用静态方法upLoadFile(MultipartFile multipartFile)就可以获取上传后文件的全路径
     *
     * @param file
     * @return 返回文件的浏览全路径
     */
    public String upLoadFile(MultipartFile file) {
        try {
            // 获取上传的文件的输入流
            InputStream inputStream = file.getInputStream();
            // 避免文件覆盖,获取文件的原始名称,如123.jpg,然后通过截取获得文件的后缀,也就是文件的类型
            String originalFilename = file.getOriginalFilename();
            //获取文件的类型
            String fileType = originalFilename.substring(originalFilename.lastIndexOf("."));
            //使用UUID工具  创建唯一名称,放置文件重名被覆盖,在拼接上上命令获取的文件类型
            String fileName = UUID.randomUUID() + fileType;
            // 指定文件上传到 COS 上的路径,即对象键。最终文件会传到存储桶名字中的images文件夹下的fileName名字
            String key = location+"/" + fileName;
            // 创建上传Object的Metadata
            ObjectMetadata objectMetadata = new ObjectMetadata();
            // - 使用输入流存储,需要设置请求长度
            objectMetadata.setContentLength(inputStream.available());
            // - 设置缓存
            objectMetadata.setCacheControl("no-cache");
            // - 设置Content-Type
            objectMetadata.setContentType(fileType);
            //上传文件
            PutObjectResult putResult = getCosClient().putObject(bucketName, key, inputStream, objectMetadata);
            // 创建文件的网络访问路径
            String url = rootSrc + key;
            //关闭 cosClient,并释放 HTTP 连接的后台管理线程
            getCosClient().shutdown();
            return url;
        } catch (Exception e) {
            e.printStackTrace();
            // 发生IO异常、COS连接异常等,返回空
            return null;
        }
    }
    /**
     * 下载文件
     * @param file
     * @return
     */
    public void downLoadFile(String file) {
        HttpServletResponse response = WebUtils.response();
        String replace = file.replace(rootSrc, "");
        response.setHeader("Access-Control-Expose-Headers","File-Type");
        COSCredentials cred = new BasicCOSCredentials(
                secretId,
                secretKey);
        // 2.1 设置存储桶的地域(上文获得)
        Region region = new Region(bucketAddr);
        ClientConfig clientConfig = new ClientConfig(region);
        // 2.2 使用https协议传输
        clientConfig.setHttpProtocol(HttpProtocol.https);
        COSClient cosClient = new COSClient(cred, clientConfig);
        try {
            // 5. 下载文件并获取输入流
            InputStream inputStream = cosClient.getObject(bucketName, replace).getObjectContent();
            ServletOutputStream outputStream = response.getOutputStream();
            // 6. 处理输入流,例如读取内容或保存到本地文件
            // 这里仅作示例,实际应用中需要根据需求处理输入流
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                // 处理读取到的数据
                outputStream.write(buffer, 0, len);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭输入流
            cosClient.shutdown();
        }
    }
    public String downLoadFileImg(String file) {
        byte[] data = null;
        String replace = file.replace(rootSrc, "");
        COSCredentials cred = new BasicCOSCredentials(
                secretId,
                secretKey);
        // 2.1 设置存储桶的地域(上文获得)
        Region region = new Region(bucketAddr);
        ClientConfig clientConfig = new ClientConfig(region);
        // 2.2 使用https协议传输
        clientConfig.setHttpProtocol(HttpProtocol.https);
        COSClient cosClient = new COSClient(cred, clientConfig);
        try {
            // 5. 下载文件并获取输入流
            InputStream inputStream = cosClient.getObject(bucketName, replace).getObjectContent();
            ByteArrayOutputStream swapStream = new ByteArrayOutputStream();
            // 6. 处理输入流,例如读取内容或保存到本地文件
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                // 处理读取到的数据
                swapStream.write(buffer, 0, len);
            }
            data = swapStream.toByteArray();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭输入流
            cosClient.shutdown();
        }
        return Base64.getEncoder().encodeToString(data);
    }
}
ruoyi-applet/src/main/resources/application-prod.yml
@@ -206,3 +206,11 @@
    accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8
    signName: "四川金达通信工程"
    templateCode: "SMS_293985284"
cos:
  client:
    accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x
    secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU
    bucket: xzgttest-1305134071
    bucketAddr: ap-chengdu
    rootSrc: https://xzgttest-1305134071.cos.ap-chengdu.myqcloud.com/
    location: xizang
ruoyi-applet/src/main/resources/application-test.yml
@@ -193,9 +193,9 @@
    accessPath: /file/
    allowExt: .jpg|.png|.gif|.jpeg|.doc|.docx|.apk|.MP4|.mp4|.pdf|.PDF
wx:
  config:
    appId: wxc3985a05da7d86dc
    secret: 5cca42633c25439613b328c08ef20cc9
  conf:
    appId: wxe91f1af7638aa5dd
    secretId: a787e1a462715604e0c9528b6d8960d1
#OSS及短信配置
code:
  config:
@@ -205,3 +205,11 @@
    accessKeySecret: 0SRb6XGkciQDPWn2rYqbJtq2qRMDY8
    signName: "四川金达通信工程"
    templateCode: "SMS_293985284"
cos:
  client:
    accessKey: AKIDCF5EF2c0DE1e5JK8r4EGJF4mNsMgp26x
    secretKey: lLl184rUyFOOE0d5KNGC3kmfNsCWk4GU
    bucket: xzgttest-1305134071
    bucketAddr: ap-chengdu
    rootSrc: https://xzgttest-1305134071.cos.ap-chengdu.myqcloud.com/
    location: xizang
ruoyi-applet/src/main/resources/mybatis-config.xml
@@ -10,7 +10,7 @@
        <setting name="cacheEnabled" value="true"/>
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
        <!-- <setting name="lazyLoadingEnabled" value="true"/> -->
        <setting name="mapUnderscoreToCamelCase" value="false"/><!--是否将map下划线方式转为驼峰式命名-->
        <setting name="mapUnderscoreToCamelCase" value="true"/><!--是否将map下划线方式转为驼峰式命名-->
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <!-- <setting name="aggressiveLazyLoading" value="false"/>-->
        <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
ruoyi-common/src/main/java/com/ruoyi/common/constant/Constants.java
@@ -83,6 +83,10 @@
     * 令牌前缀
     */
    public static final String LOGIN_USER_KEY = "login_user_key";
    /**
     * 小程序
     */
    public static final String LOGIN_USER_APPLET_KEY = "login_user_applet_key";
    /**
     * 用户ID
@@ -128,6 +132,10 @@
     * LDAPS 远程方法调用
     */
    public static final String LOOKUP_LDAPS = "ldaps:";
    /**
     * LDAPS 远程方法调用
     */
    public static final String INFORMATION_VIEW = "information_view:";
    /**
     * 自动识别json对象白名单配置(仅允许解析的包名,范围越小越安全)
ruoyi-common/src/main/java/com/ruoyi/common/constant/DictConstants.java
@@ -43,4 +43,8 @@
     * 账单类型 1=租金 2=押金 3=生活费用
     */
    public static final String DICT_TYPE_BILL_TYPE = "t_bill_type";
    /**
     * 验收记录情况 1=良好 2=一般 3=较差
     */
    public static final String DICT_TYPE_CHECK_SITUATION = "t_check_situation";
}
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/SysUser.java
@@ -125,8 +125,11 @@
    @TableField(exist = false)
    private String roleName;
    @TableField(exist = false)
    private String deptName;
    @ApiModelProperty(value = "部门id集合")
    @TableField(exist = false)
    private List<String> deptIds;
    public String getRoleName() {
        return roleName;
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/entity/TTenantResp.java
New file
@@ -0,0 +1,70 @@
package com.ruoyi.common.core.domain.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.common.core.domain.BaseModel;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import java.time.LocalDate;
/**
 * <p>
 * 租户
 * </p>
 *
 * @author xiaochen
 * @since 2025-01-20
 */
@Data
@EqualsAndHashCode(callSuper = false)
@ApiModel(value="TTenant对象", description="登录返回")
public class TTenantResp  {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.ASSIGN_ID)
    private String id;
    @ApiModelProperty(value = "住户名称")
    private String residentName;
    @ApiModelProperty(value = "入住时间")
    @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
    private LocalDate checkinTime;
    @ApiModelProperty(value = "租户属性")
    private String tenantAttributes;
    @ApiModelProperty(value = "租户类型")
    private String tenantType;
    @ApiModelProperty(value = "联系电话")
    private String phone;
    @ApiModelProperty(value = "证件号码")
    private String idCard;
    @ApiModelProperty(value = "邮箱")
    private String email;
    @ApiModelProperty(value = "银行转账专号")
    private String bankNumber;
    @ApiModelProperty(value = "通讯地址")
    private String mailAddress;
    @ApiModelProperty(value = "登录账号")
    private String account;
    @ApiModelProperty(value = "登录密码")
    private String password;
    @ApiModelProperty(value = "微信openid")
    private String openId;
}
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/model/LoginUserApplet.java
New file
@@ -0,0 +1,263 @@
package com.ruoyi.common.core.domain.model;
import com.alibaba.fastjson2.annotation.JSONField;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.entity.TTenantResp;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Set;
/**
 * 登录用户身份权限
 *
 * @author ruoyi
 */
@Data
public class LoginUserApplet implements UserDetails
{
    private static final long serialVersionUID = 1L;
    /**
     * 用户ID
     */
    private String userId;
    /**
     * 部门ID
     */
    private Long deptId;
    /**
     * 用户唯一标识
     */
    private String token;
    /**
     * 登录时间
     */
    private Long loginTime;
    /**
     * 过期时间
     */
    private Long expireTime;
    /**
     * 登录IP地址
     */
    private String ipaddr;
    /**
     * 登录地点
     */
    private String loginLocation;
    /**
     * 浏览器类型
     */
    private String browser;
    /**
     * 操作系统
     */
    private String os;
    /**
     * 权限列表
     */
    private Set<String> permissions;
    /**
     * 用户信息
     */
    private TTenantResp user;
    public LoginUserApplet()
    {
    }
    public LoginUserApplet(TTenantResp user, Set<String> permissions)
    {
        this.user = user;
        this.permissions = permissions;
    }
    public LoginUserApplet(String userId, Long deptId, TTenantResp user, Set<String> permissions)
    {
        this.userId = userId;
        this.deptId = deptId;
        this.user = user;
        this.permissions = permissions;
    }
    public String getUserId()
    {
        return userId;
    }
    public void setUserId(String userId)
    {
        this.userId = userId;
    }
    public Long getDeptId()
    {
        return deptId;
    }
    public void setDeptId(Long deptId)
    {
        this.deptId = deptId;
    }
    public String getToken()
    {
        return token;
    }
    public void setToken(String token)
    {
        this.token = token;
    }
    @JSONField(serialize = false)
    @Override
    public String getPassword()
    {
        return user.getPassword();
    }
    @Override
    public String getUsername()
    {
        return user.getResidentName();
    }
    /**
     * 账户是否未过期,过期无法验证
     */
    @JSONField(serialize = false)
    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }
    /**
     * 指定用户是否解锁,锁定的用户无法进行身份验证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }
    /**
     * 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }
    /**
     * 是否可用 ,禁用的用户不能身份验证
     *
     * @return
     */
    @JSONField(serialize = false)
    @Override
    public boolean isEnabled()
    {
        return true;
    }
    public Long getLoginTime()
    {
        return loginTime;
    }
    public void setLoginTime(Long loginTime)
    {
        this.loginTime = loginTime;
    }
    public String getIpaddr()
    {
        return ipaddr;
    }
    public void setIpaddr(String ipaddr)
    {
        this.ipaddr = ipaddr;
    }
    public String getLoginLocation()
    {
        return loginLocation;
    }
    public void setLoginLocation(String loginLocation)
    {
        this.loginLocation = loginLocation;
    }
    public String getBrowser()
    {
        return browser;
    }
    public void setBrowser(String browser)
    {
        this.browser = browser;
    }
    public String getOs()
    {
        return os;
    }
    public void setOs(String os)
    {
        this.os = os;
    }
    public Long getExpireTime()
    {
        return expireTime;
    }
    public void setExpireTime(Long expireTime)
    {
        this.expireTime = expireTime;
    }
    public Set<String> getPermissions()
    {
        return permissions;
    }
    public void setPermissions(Set<String> permissions)
    {
        this.permissions = permissions;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return null;
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/core/exception/ServiceException.java
New file
@@ -0,0 +1,74 @@
package com.ruoyi.common.core.exception;
/**
 * 业务异常
 *
 * @author ruoyi
 */
public final class ServiceException extends RuntimeException
{
    private static final long serialVersionUID = 1L;
    /**
     * 错误码
     */
    private Integer code;
    /**
     * 错误提示
     */
    private String message;
    /**
     * 错误明细,内部调试错误
     *
     * 和 {@link CommonResult#getDetailMessage()} 一致的设计
     */
    private String detailMessage;
    /**
     * 空构造方法,避免反序列化问题
     */
    public ServiceException()
    {
    }
    public ServiceException(String message)
    {
        this.message = message;
    }
    public ServiceException(String message, Integer code)
    {
        this.message = message;
        this.code = code;
    }
    public String getDetailMessage()
    {
        return detailMessage;
    }
    @Override
    public String getMessage()
    {
        return message;
    }
    public Integer getCode()
    {
        return code;
    }
    public ServiceException setMessage(String message)
    {
        this.message = message;
        return this;
    }
    public ServiceException setDetailMessage(String detailMessage)
    {
        this.detailMessage = detailMessage;
        return this;
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/core/redis/RedisCache.java
@@ -330,4 +330,14 @@
        Long result = (Long) redisTemplate.execute(UNLOCK_SCRIPT, Collections.singletonList(lockKeyWithPrefix), requestId);
        return result != null && result == 1L;
    }
    /**
     * 自增
     * @param key 要加一的键
     */
    public int increment(String key) {
        // +1 操作
        return redisTemplate.opsForValue().increment(key).intValue();
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/core/utils/Constants.java
New file
@@ -0,0 +1,143 @@
package com.ruoyi.common.core.utils;
/**
 * 通用常量信息
 *
 * @author ruoyi
 */
public class Constants
{
    /**
     * UTF-8 字符集
     */
    public static final String UTF8 = "UTF-8";
    /**
     * GBK 字符集
     */
    public static final String GBK = "GBK";
    /**
     * www主域
     */
    public static final String WWW = "www.";
    /**
     * RMI 远程方法调用
     */
    public static final String LOOKUP_RMI = "rmi:";
    /**
     * LDAP 远程方法调用
     */
    public static final String LOOKUP_LDAP = "ldap:";
    /**
     * LDAPS 远程方法调用
     */
    public static final String LOOKUP_LDAPS = "ldaps:";
    /**
     * http请求
     */
    public static final String HTTP = "http://";
    /**
     * https请求
     */
    public static final String HTTPS = "https://";
    /**
     * 成功标记
     */
    public static final Integer SUCCESS = 200;
    /**
     * 失败标记
     */
    public static final Integer FAIL = 500;
    /**
     * 登录成功状态
     */
    public static final String LOGIN_SUCCESS_STATUS = "1";
    /**
     * 登录失败状态
     */
    public static final String LOGIN_FAIL_STATUS = "2";
    /**
     * 登录成功
     */
    public static final String LOGIN_SUCCESS = "Success";
    /**
     * 注销
     */
    public static final String LOGOUT = "Logout";
    /**
     * 注册
     */
    public static final String REGISTER = "Register";
    /**
     * 登录失败
     */
    public static final String LOGIN_FAIL = "Error";
    /**
     * 当前记录起始索引
     */
    public static final String PAGE_NUM = "pageNum";
    /**
     * 每页显示记录数
     */
    public static final String PAGE_SIZE = "pageSize";
    /**
     * 排序列
     */
    public static final String ORDER_BY_COLUMN = "orderByColumn";
    /**
     * 排序的方向 "desc" 或者 "asc".
     */
    public static final String IS_ASC = "isAsc";
    /**
     * 验证码有效期(分钟)
     */
    public static final long CAPTCHA_EXPIRATION = 2;
    /**
     * 资源映射路径 前缀
     */
    public static final String RESOURCE_PREFIX = "/profile";
    /**
     * 定时任务白名单配置(仅允许访问的包名,如其他需要可以自行添加)
     */
    public static final String[] JOB_WHITELIST_STR = { "com.ruoyi" };
    /**
     * 时间格式化
     */
    public static final String DATE_FORMATTER_TIME = "yyyy-MM-dd HH:mm:ss";
    public static final String DATE_FORMATTER_DATE = "yyyy-MM-dd";
    /**
     * 修改手机号后缀
     */
    public static final String UPDATE_PHONE = "_updatePhone";
    /**
     * 申请建桩后缀
     */
    public static final String APPLY_CHARGING = "_applyCharging";
    /**
     * 定时任务违规的字符
     */
    public static final String[] JOB_ERROR_STR = { "java.net.URL", "javax.naming.InitialContext", "org.yaml.snakeyaml",
            "org.springframework", "org.apache", "com.ruoyi.common.core.utils.file" };
}
ruoyi-common/src/main/java/com/ruoyi/common/core/utils/HttpUtils.java
New file
@@ -0,0 +1,311 @@
package com.ruoyi.common.core.utils;
import com.ruoyi.common.core.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.*;
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.security.cert.X509Certificate;
/**
 * 通用http发送方法
 *
 * @author ruoyi
 */
public class HttpUtils
{
    private static final Logger log = LoggerFactory.getLogger(HttpUtils.class);
    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url)
    {
        return sendGet(url, StringUtils.EMPTY);
    }
    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param)
    {
        return sendGet(url, param, Constants.UTF8);
    }
    /**
     * 向指定 URL 发送GET方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @param contentType 编码类型
     * @return 所代表远程资源的响应结果
     */
    public static String sendGet(String url, String param, String contentType)
    {
        StringBuilder result = new StringBuilder();
        BufferedReader in = null;
        try
        {
            String urlNameString = StringUtils.isNotBlank(param) ? url + "?" + param : url;
            log.info("sendGet - {}", urlNameString);
            URL realUrl = new URL(urlNameString);
            URLConnection connection = realUrl.openConnection();
            connection.setRequestProperty("accept", "*/*");
            connection.setRequestProperty("connection", "Keep-Alive");
            connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            connection.connect();
            in = new BufferedReader(new InputStreamReader(connection.getInputStream(), contentType));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendGet ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendGet SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendGet IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendGet Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (in != null)
                {
                    in.close();
                }
            }
            catch (Exception ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }
    /**
     * 向指定 URL 发送POST方法的请求
     *
     * @param url 发送请求的 URL
     * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。
     * @return 所代表远程资源的响应结果
     */
    public static String sendPost(String url, String param)
    {
        PrintWriter out = null;
        BufferedReader in = null;
        StringBuilder result = new StringBuilder();
        try
        {
            log.info("sendPost - {}", url);
            URL realUrl = new URL(url);
            URLConnection conn = realUrl.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            out = new PrintWriter(conn.getOutputStream());
            out.print(param);
            out.flush();
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
            String line;
            while ((line = in.readLine()) != null)
            {
                result.append(line);
            }
            log.info("recv - {}", result);
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendPost Exception, url=" + url + ",param=" + param, e);
        }
        finally
        {
            try
            {
                if (out != null)
                {
                    out.close();
                }
                if (in != null)
                {
                    in.close();
                }
            }
            catch (IOException ex)
            {
                log.error("调用in.close Exception, url=" + url + ",param=" + param, ex);
            }
        }
        return result.toString();
    }
    public static String sendSSLPost(String url, String param)
    {
        StringBuilder result = new StringBuilder();
        String urlNameString = url + "?" + param;
        try
        {
            log.info("sendSSLPost - {}", urlNameString);
            SSLContext sc = SSLContext.getInstance("SSL");
            sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
            URL console = new URL(urlNameString);
            HttpsURLConnection conn = (HttpsURLConnection) console.openConnection();
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
            conn.setRequestProperty("Accept-Charset", "utf-8");
            conn.setRequestProperty("contentType", "utf-8");
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setSSLSocketFactory(sc.getSocketFactory());
            conn.setHostnameVerifier(new TrustAnyHostnameVerifier());
            conn.connect();
            InputStream is = conn.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(is));
            String ret = "";
            while ((ret = br.readLine()) != null)
            {
                if (ret != null && !"".equals(ret.trim()))
                {
                    result.append(new String(ret.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8));
                }
            }
            log.info("recv - {}", result);
            conn.disconnect();
            br.close();
        }
        catch (ConnectException e)
        {
            log.error("调用HttpUtils.sendSSLPost ConnectException, url=" + url + ",param=" + param, e);
        }
        catch (SocketTimeoutException e)
        {
            log.error("调用HttpUtils.sendSSLPost SocketTimeoutException, url=" + url + ",param=" + param, e);
        }
        catch (IOException e)
        {
            log.error("调用HttpUtils.sendSSLPost IOException, url=" + url + ",param=" + param, e);
        }
        catch (Exception e)
        {
            log.error("调用HttpsUtil.sendSSLPost Exception, url=" + url + ",param=" + param, e);
        }
        return result.toString();
    }
    public static String post(String strURL, String params) {
        String result = "";
        BufferedReader reader = null;
        try {
            URL url = new URL(strURL);// 创建连接
            HttpURLConnection connection = (HttpURLConnection) url
                    .openConnection();
            connection.setDoOutput(true);
            connection.setDoInput(true);
            connection.setUseCaches(false);
            connection.setInstanceFollowRedirects(true);
            connection.setRequestMethod("POST"); // 设置请求方式
            connection.setRequestProperty("Accept", "application/json"); // 设置接收数据的格式
            connection.setRequestProperty("Content-Type", "application/json"); // 设置发送数据的格式
            connection.connect();
            if (params != null && !StringUtils.isEmpty(params)) {
                byte[] writebytes = params.getBytes();
                // 设置文件长度
                //   connection.setRequestProperty("Content-Length", String.valueOf(writebytes.length));
                OutputStream outwritestream = connection.getOutputStream();
                outwritestream.write(params.getBytes());
                outwritestream.flush();
                outwritestream.close();
                // Log.d("hlhupload", "doJsonPost: conn"+connection.getResponseCode());
            }
            if (connection.getResponseCode() == 200) {
                log.info("<<<<<<<<<<<<<请求响应:{}", connection.getResponseMessage());
                reader = new BufferedReader(
                        new InputStreamReader(connection.getInputStream()));
                result = reader.readLine();
                log.info("<<<<<<<<<<<<<请求响应:{}", result);
            } else {
                throw new ServiceException(connection.getResponseMessage());
            }
        } catch (Exception e) {
            throw new ServiceException("http的post请求异常!" + e.getMessage());
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return result;
    }
    private static class TrustAnyTrustManager implements X509TrustManager
    {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType)
        {
        }
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType)
        {
        }
        @Override
        public X509Certificate[] getAcceptedIssuers()
        {
            return new X509Certificate[] {};
        }
    }
    private static class TrustAnyHostnameVerifier implements HostnameVerifier
    {
        @Override
        public boolean verify(String hostname, SSLSession session)
        {
            return true;
        }
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/redis/configure/FastJson2JsonRedisSerializer.java
New file
@@ -0,0 +1,50 @@
package com.ruoyi.common.redis.configure;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONReader;
import com.alibaba.fastjson2.JSONWriter;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.SerializationException;
import java.nio.charset.Charset;
/**
 * Redis使用FastJson序列化
 *
 * @author ruoyi
 */
public class FastJson2JsonRedisSerializer<T> implements RedisSerializer<T>
{
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    private Class<T> clazz;
    public FastJson2JsonRedisSerializer(Class<T> clazz)
    {
        super();
        this.clazz = clazz;
    }
    @Override
    public byte[] serialize(T t) throws SerializationException
    {
        if (t == null)
        {
            return new byte[0];
        }
        return JSON.toJSONString(t, JSONWriter.Feature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }
    @Override
    public T deserialize(byte[] bytes) throws SerializationException
    {
        if (bytes == null || bytes.length <= 0)
        {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz, JSONReader.Feature.SupportAutoType);
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/redis/service/RedisService.java
New file
@@ -0,0 +1,273 @@
package com.ruoyi.common.redis.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundSetOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
 * spring redis 工具类
 *
 * @author ruoyi
 **/
@SuppressWarnings(value = { "unchecked", "rawtypes" })
@Component
public class RedisService
{
    @Autowired
    public RedisTemplate redisTemplate;
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     */
    public <T> void setCacheObject(final String key, final T value)
    {
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 缓存基本的对象,Integer、String、实体类等
     *
     * @param key 缓存的键值
     * @param value 缓存的值
     * @param timeout 时间
     * @param timeUnit 时间颗粒度
     */
    public <T> void setCacheObject(final String key, final T value, final Long timeout, final TimeUnit timeUnit)
    {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout)
    {
        return expire(key, timeout, TimeUnit.SECONDS);
    }
    /**
     * 设置有效时间
     *
     * @param key Redis键
     * @param timeout 超时时间
     * @param unit 时间单位
     * @return true=设置成功;false=设置失败
     */
    public boolean expire(final String key, final long timeout, final TimeUnit unit)
    {
        return redisTemplate.expire(key, timeout, unit);
    }
    /**
     * 获取有效时间
     *
     * @param key Redis键
     * @return 有效时间
     */
    public long getExpire(final String key)
    {
        return redisTemplate.getExpire(key);
    }
    /**
     * 判断 key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public Boolean hasKey(String key)
    {
        return redisTemplate.hasKey(key);
    }
    /**
     * 获得缓存的基本对象。
     *
     * @param key 缓存键值
     * @return 缓存键值对应的数据
     */
    public <T> T getCacheObject(final String key)
    {
        ValueOperations<String, T> operation = redisTemplate.opsForValue();
        return operation.get(key);
    }
    /**
     * 删除单个对象
     *
     * @param key
     */
    public boolean deleteObject(final String key)
    {
        return redisTemplate.delete(key);
    }
    /**
     * 删除集合对象
     *
     * @param collection 多个对象
     * @return
     */
    public boolean deleteObject(final Collection collection)
    {
        return redisTemplate.delete(collection) > 0;
    }
    /**
     * 缓存List数据
     *
     * @param key 缓存的键值
     * @param dataList 待缓存的List数据
     * @return 缓存的对象
     */
    public <T> long setCacheList(final String key, final List<T> dataList)
    {
        Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
        return count == null ? 0 : count;
    }
    /**
     * 获得缓存的list对象
     *
     * @param key 缓存的键值
     * @return 缓存键值对应的数据
     */
    public <T> List<T> getCacheList(final String key)
    {
        return redisTemplate.opsForList().range(key, 0, -1);
    }
    /**
     * 缓存Set
     *
     * @param key 缓存键值
     * @param dataSet 缓存的数据
     * @return 缓存数据的对象
     */
    public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet)
    {
        BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
        Iterator<T> it = dataSet.iterator();
        while (it.hasNext())
        {
            setOperation.add(it.next());
        }
        return setOperation;
    }
    /**
     * 获得缓存的set
     *
     * @param key
     * @return
     */
    public <T> Set<T> getCacheSet(final String key)
    {
        return redisTemplate.opsForSet().members(key);
    }
    /**
     * 缓存Map
     *
     * @param key
     * @param dataMap
     */
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
        }
    }
    public <T> void setCacheMap(final String key, final Map<String, T> dataMap, long timeout)
    {
        if (dataMap != null) {
            redisTemplate.opsForHash().putAll(key, dataMap);
            redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
        }
    }
    /**
     * 获得缓存的Map
     *
     * @param key
     * @return
     */
    public <T> Map<String, T> getCacheMap(final String key)
    {
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * 往Hash中存入数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @param value 值
     */
    public <T> void setCacheMapValue(final String key, final String hKey, final T value)
    {
        redisTemplate.opsForHash().put(key, hKey, value);
    }
    /**
     * 获取Hash中的数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return Hash中的对象
     */
    public <T> T getCacheMapValue(final String key, final String hKey)
    {
        HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
        return opsForHash.get(key, hKey);
    }
    /**
     * 获取多个Hash中的数据
     *
     * @param key Redis键
     * @param hKeys Hash键集合
     * @return Hash对象集合
     */
    public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys)
    {
        return redisTemplate.opsForHash().multiGet(key, hKeys);
    }
    /**
     * 删除Hash中的某条数据
     *
     * @param key Redis键
     * @param hKey Hash键
     * @return 是否成功
     */
    public boolean deleteCacheMapValue(final String key, final String hKey)
    {
        return redisTemplate.opsForHash().delete(key, hKey) > 0;
    }
    /**
     * 获得缓存的基本对象列表
     *
     * @param pattern 字符串前缀
     * @return 对象列表
     */
    public Collection<String> keys(final String pattern)
    {
        return redisTemplate.keys(pattern);
    }
}
ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java
@@ -118,6 +118,7 @@
                        "/applet/changepwd", "/captchaImage","/getCode","/loginCode",
                        "/operations/getBySingleNum/**",
                        "/user/getUserInfoByNumber/**",
                        "/wxLogin/**",
                        "/open/**"
                ).permitAll()
                // 静态资源,可匿名访问
ruoyi-framework/src/main/java/com/ruoyi/framework/security/filter/JwtAuthenticationTokenFilter.java
@@ -5,6 +5,8 @@
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.core.domain.model.LoginUserApplet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -32,12 +34,22 @@
            throws ServletException, IOException
    {
        LoginUser loginUser = tokenService.getLoginUser(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication()))
        LoginUserApplet applet = tokenService.getLoginUserApplet(request);
        if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())||
                StringUtils.isNotNull(applet))
        {
            tokenService.verifyToken(loginUser);
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
            authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            if (StringUtils.isNotNull(loginUser)){
                tokenService.verifyToken(loginUser);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
            if (StringUtils.isNotNull(applet)){
                tokenService.verifyTokenApplet(applet);
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(applet, null, applet.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
@@ -4,6 +4,8 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import com.ruoyi.common.core.domain.model.LoginUserApplet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -66,6 +68,15 @@
        return getLoginUser(ServletUtils.getRequest());
    }
    /**
     * 小程序获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUserApplet getLoginUserApplet()
    {
        return getLoginUserApplet(ServletUtils.getRequest());
    }
    /**
     * 获取用户身份信息
     *
     * @return 用户信息
@@ -83,6 +94,33 @@
                String uuid = (String) claims.get(Constants.LOGIN_USER_KEY);
                String userKey = getTokenKey(uuid);
                LoginUser user = redisCache.getCacheObject(userKey);
                return user;
            }
            catch (Exception e)
            {
                log.error("获取用户信息异常'{}'", e.getMessage());
            }
        }
        return null;
    }
    /**
     * 小程序获取用户身份信息
     *
     * @return 用户信息
     */
    public LoginUserApplet getLoginUserApplet(HttpServletRequest request)
    {
        // 获取请求携带的令牌
        String token = getToken(request);
        if (StringUtils.isNotEmpty(token))
        {
            try
            {
                Claims claims = parseToken(token);
                // 解析对应的权限以及用户信息
                String uuid = (String) claims.get(Constants.LOGIN_USER_APPLET_KEY);
                String userKey = getTokenKey(uuid);
                LoginUserApplet user = redisCache.getCacheObject(userKey);
                return user;
            }
            catch (Exception e)
@@ -133,6 +171,23 @@
        claims.put(Constants.LOGIN_USER_KEY, token);
        return createToken(claims);
    }
    /**
     * 创建用户小程序令牌
     *
     * @param loginUser 用户信息
     * @return 令牌
     */
    public String createTokenApplet(LoginUserApplet loginUser)
    {
        String token = IdUtils.fastUUID();
        loginUser.setToken(token);
        setUserAgentApplet(loginUser);
        refreshTokenApplet(loginUser);
        Map<String, Object> claims = new HashMap<>();
        claims.put(Constants.LOGIN_USER_APPLET_KEY, token);
        return createTokenApplet(claims);
    }
    /**
     * 验证令牌有效期,相差不足20分钟,自动刷新缓存
@@ -149,6 +204,21 @@
            refreshToken(loginUser);
        }
    }
    /**
     * 小程序验证令牌有效期,相差不足20分钟,自动刷新缓存
     *
     * @param loginUser
     * @return 令牌
     */
    public void verifyTokenApplet(LoginUserApplet loginUser)
    {
        long expireTime = loginUser.getExpireTime();
        long currentTime = System.currentTimeMillis();
        if (expireTime - currentTime <= MILLIS_MINUTE_TEN)
        {
            refreshTokenApplet(loginUser);
        }
    }
    /**
     * 刷新令牌有效期
@@ -156,6 +226,19 @@
     * @param loginUser 登录信息
     */
    public void refreshToken(LoginUser loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
        // 根据uuid将loginUser缓存
        String userKey = getTokenKey(loginUser.getToken());
        redisCache.setCacheObject(userKey, loginUser, expireTime, TimeUnit.MINUTES);
    }
    /**
     * 刷新令牌有效期
     *
     * @param loginUser 登录信息
     */
    public void refreshTokenApplet(LoginUserApplet loginUser)
    {
        loginUser.setLoginTime(System.currentTimeMillis());
        loginUser.setExpireTime(loginUser.getLoginTime() + expireTime * MILLIS_MINUTE);
@@ -178,6 +261,20 @@
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }
    /**
     * 设置用户代理信息
     *
     * @param loginUser 登录信息
     */
    public void setUserAgentApplet(LoginUserApplet loginUser)
    {
        UserAgent userAgent = UserAgent.parseUserAgentString(ServletUtils.getRequest().getHeader("User-Agent"));
        String ip = IpUtils.getIpAddr();
        loginUser.setIpaddr(ip);
        loginUser.setLoginLocation(AddressUtils.getRealAddressByIP(ip));
        loginUser.setBrowser(userAgent.getBrowser().getName());
        loginUser.setOs(userAgent.getOperatingSystem().getName());
    }
    /**
     * 从数据声明生成令牌
@@ -192,6 +289,19 @@
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }
    /**
     * 小程序从数据声明生成令牌
     *
     * @param claims 数据声明
     * @return 令牌
     */
    private String createTokenApplet(Map<String, Object> claims)
    {
        String token = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, secret).compact();
        return token;
    }
    /**
     * 从令牌中获取数据声明
ruoyi-system/pom.xml
@@ -79,6 +79,10 @@
            <artifactId>bankapi</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.datatype</groupId>
            <artifactId>jackson-datatype-jsr310</artifactId>
        </dependency>
    </dependencies>
</project>
ruoyi-system/src/main/java/com/ruoyi/system/dto/TFaultRepairMessageDTO.java
New file
@@ -0,0 +1,10 @@
package com.ruoyi.system.dto;
import com.ruoyi.system.model.TFaultRepairMessage;
import io.swagger.annotations.ApiModel;
import lombok.Data;
@Data
@ApiModel(value = "报修dto")
public class TFaultRepairMessageDTO extends TFaultRepairMessage {
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/SysUserRoleMapper.java
@@ -2,6 +2,7 @@
import java.util.List;
import com.ruoyi.system.model.TDeptToUser;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.system.domain.SysUserRole;
@@ -70,4 +71,6 @@
     * @return 结果
     */
    public int deleteUserRoleInfos(@Param("roleId") Long roleId, @Param("userIds") Long[] userIds);
    void insertBatchUserDept(List<TDeptToUser> deptToUserList);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TCheckAcceptRecordMapper.java
@@ -1,7 +1,13 @@
package com.ruoyi.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TCheckAcceptRecord;
import com.ruoyi.system.query.TCheckAcceptRecordQuery;
import com.ruoyi.system.vo.TCheckAcceptRecordVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -13,4 +19,13 @@
 */
public interface TCheckAcceptRecordMapper extends BaseMapper<TCheckAcceptRecord> {
    /**
     * 分页查询验收记录
     * @param query
     * @param pageInfo
     * @return
     */
    List<TCheckAcceptRecordVO> pageList(@Param("query") TCheckAcceptRecordQuery query, @Param("pageInfo")PageInfo<TCheckAcceptRecordVO> pageInfo);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TDeptToUserMapper.java
@@ -2,6 +2,7 @@
import com.ruoyi.system.model.TDeptToUser;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
@@ -11,6 +12,14 @@
 * @author xiaochen
 * @since 2025-02-06
 */
@Mapper
public interface TDeptToUserMapper extends BaseMapper<TDeptToUser> {
    /**
     * 根据用户ID删除部门用户中间表
     *
     * @param userId 用户ID
     * @return 删除结果
     **/
    int deleteUserDeptByUserId(Long userId);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TFaultAreaDicMapper.java
@@ -4,6 +4,7 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TFaultAreaDic;
import com.ruoyi.system.query.TFaultAreaDicQuery;
import com.ruoyi.system.vo.TFaultAreaDicVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -28,4 +29,9 @@
     */
    List<TFaultAreaDic> pageList(@Param("query")TFaultAreaDicQuery query, @Param("pageInfo")PageInfo<TFaultAreaDic> pageInfo);
    /**
     * 查询列表
     * @return
     */
    List<TFaultAreaDicVO> getAreaDicList();
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TFaultRepairMessageMapper.java
@@ -1,7 +1,13 @@
package com.ruoyi.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TFaultRepairMessage;
import com.ruoyi.system.query.TFaultRepairMessageQuery;
import com.ruoyi.system.vo.TFaultRepairMessageVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
 * <p>
@@ -13,4 +19,19 @@
 */
public interface TFaultRepairMessageMapper extends BaseMapper<TFaultRepairMessage> {
    /**
     * 根据id查询报修管理
     * @param id
     * @return
     */
    TFaultRepairMessageVO getDetailById(@Param("id") String id);
    /**
     * 分页查询报修管理
     * @param query
     * @param pageInfo
     * @return
     */
    List<TFaultRepairMessageVO> pageList(@Param("query") TFaultRepairMessageQuery query, @Param("pageInfo")PageInfo<TFaultRepairMessageVO> pageInfo);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/TItemTypeMapper.java
@@ -4,6 +4,7 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TItemType;
import com.ruoyi.system.query.TItemTypeQuery;
import com.ruoyi.system.vo.TItemTypeVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@@ -25,4 +26,8 @@
     * @return
     */
    List<TItemType> pageList(@Param("query") TItemTypeQuery query, @Param("pageInfo")PageInfo<TItemType> pageInfo);
    List<TItemTypeVO> getItemList();
}
ruoyi-system/src/main/java/com/ruoyi/system/model/TCheckAcceptRecord.java
@@ -84,7 +84,7 @@
    @TableField("pictures")
    private String pictures;
    @ApiModelProperty(value = "验收结果 1=合格 2=不合格")
    @ApiModelProperty(value = "验收结果 1=合格 0=不合格")
    @TableField("check_result")
    private Boolean checkResult;
ruoyi-system/src/main/java/com/ruoyi/system/model/TContract.java
@@ -133,6 +133,16 @@
    @TableField("remark")
    private String remark;
    /**
     * 1    待提交
     * 2    待审批
     * 3    未签订
     * 4    已签订
     * 5    已驳回
     * 6    已终止
     * 7    待结算
     * 8    已结算
     */
    @ApiModelProperty(value = "状态 待提交 待审批 未签订 已签订....")
    @TableField("status")
    private String status;
@@ -151,5 +161,8 @@
    @ApiModelProperty(value = "合计年租金")
    @TableField("total_year")
    private BigDecimal totalYear;
    @ApiModelProperty(value = "房屋名称")
    @TableField(exist = false)
    private String houseName;
}
ruoyi-system/src/main/java/com/ruoyi/system/model/TFaultRepairMessage.java
@@ -34,12 +34,20 @@
    private String id;
    @ApiModelProperty(value = "用户id")
    @TableField("app_user_id")
    private String appUserId;
    @TableField("tenant_id")
    private String tenantId;
    @ApiModelProperty(value = "物品分类id")
    @TableField("item_type_id")
    private String itemTypeId;
    @ApiModelProperty(value = "物品id")
    @TableField("item_id")
    private String itemId;
    @ApiModelProperty(value = "合同id")
    @TableField("contract_id")
    private String contractId;
    @ApiModelProperty(value = "故障区域")
    @TableField("fault_area_name")
@@ -99,6 +107,10 @@
    @TableField("attachment")
    private String attachment;
    @ApiModelProperty(value = "附件名称  逗号分割")
    @TableField("attachment_name")
    private String attachmentName;
    @ApiModelProperty(value = "状态 1=待处理 2=已处理")
    @TableField("status")
    private Integer status;
ruoyi-system/src/main/java/com/ruoyi/system/model/TInformation.java
@@ -67,4 +67,8 @@
    @TableField("attachment_name")
    private String attachmentName;
    @ApiModelProperty(value = "查看次数")
    @TableField(exist = false)
    private Integer viewCount;
}
ruoyi-system/src/main/java/com/ruoyi/system/model/TTenant.java
@@ -88,5 +88,8 @@
    @ApiModelProperty(value = "登录密码")
    @TableField("password")
    private String password;
    @ApiModelProperty(value = "微信openid")
    @TableField("open_id")
    private String openId;
}
ruoyi-system/src/main/java/com/ruoyi/system/query/SysUserQuery.java
@@ -5,18 +5,20 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(value = "账户列表query")
public class SysUserQuery extends BasePage {
    @ApiModelProperty(value = "姓名")
    private String nickName;
    private String nickNameAndPhone;
    @ApiModelProperty(value = "角色id")
    private Integer roleId;
    @ApiModelProperty(value = "手机号")
    private String phonenumber;
    @ApiModelProperty(value = "部门id集合")
    private List<String> deptIds;
    @ApiModelProperty(value = "状态  0=正常 1=停用")
    private String status;
ruoyi-system/src/main/java/com/ruoyi/system/query/TCheckAcceptRecordQuery.java
New file
@@ -0,0 +1,22 @@
package com.ruoyi.system.query;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.common.core.domain.model.TimeRangeQueryBody;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "验收记录查询对象query")
public class TCheckAcceptRecordQuery extends TimeRangeQueryBody {
    @ApiModelProperty(value = "房屋名称")
    private String houseName;
    @ApiModelProperty(value = "合同编号")
    private String contractNumber;
    @ApiModelProperty(value = "验收结果 1=合格 0=不合格")
    private Boolean checkResult;
}
ruoyi-system/src/main/java/com/ruoyi/system/query/TFaultRepairMessageQuery.java
New file
@@ -0,0 +1,27 @@
package com.ruoyi.system.query;
import com.baomidou.mybatisplus.annotation.TableField;
import com.ruoyi.common.core.domain.BasePage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "报修列表查询query")
public class TFaultRepairMessageQuery extends BasePage {
    @ApiModelProperty(value = "用户id")
    private String tenantId;
    @ApiModelProperty(value = "联系电话")
    private String contactNumber;
    @ApiModelProperty(value = "状态 1=待处理 2=已处理")
    private Integer status;
    @ApiModelProperty(value = "报修类型 1=常规维修 2=紧急抢修")
    private Integer repairType;
    @ApiModelProperty(value = "处理人")
    private String handlePerson;
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TCheckAcceptRecordService.java
@@ -1,7 +1,10 @@
package com.ruoyi.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TCheckAcceptRecord;
import com.ruoyi.system.query.TCheckAcceptRecordQuery;
import com.ruoyi.system.vo.TCheckAcceptRecordVO;
/**
 * <p>
@@ -13,4 +16,10 @@
 */
public interface TCheckAcceptRecordService extends IService<TCheckAcceptRecord> {
    /**
     * 获取验收记录分页列表
     * @param query
     * @return
     */
    PageInfo<TCheckAcceptRecordVO> pageList(TCheckAcceptRecordQuery query);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TFaultAreaDicService.java
@@ -4,6 +4,9 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TFaultAreaDic;
import com.ruoyi.system.query.TFaultAreaDicQuery;
import com.ruoyi.system.vo.TFaultAreaDicVO;
import java.util.List;
/**
 * <p>
@@ -28,4 +31,7 @@
     * @return
     */
    boolean isExit(TFaultAreaDic dto);
    List<TFaultAreaDicVO> getAreaDicList();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TFaultRepairMessageService.java
@@ -1,7 +1,10 @@
package com.ruoyi.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TFaultRepairMessage;
import com.ruoyi.system.query.TFaultRepairMessageQuery;
import com.ruoyi.system.vo.TFaultRepairMessageVO;
/**
 * <p>
@@ -13,4 +16,17 @@
 */
public interface TFaultRepairMessageService extends IService<TFaultRepairMessage> {
    /**
     * 查看报修详情
     * @param id
     * @return
     */
    TFaultRepairMessageVO getDetailById(String id);
    /**
     * 获取报修分页列表
     * @param query
     * @return
     */
    PageInfo<TFaultRepairMessageVO> pageList(TFaultRepairMessageQuery query);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TItemTypeService.java
@@ -4,6 +4,9 @@
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.model.TItemType;
import com.ruoyi.system.query.TItemTypeQuery;
import com.ruoyi.system.vo.TItemTypeVO;
import java.util.List;
/**
 * <p>
@@ -28,4 +31,6 @@
     * @return
     */
    PageInfo<TItemType> pageList(TItemTypeQuery query);
    List<TItemTypeVO> getItemList();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/TTenantService.java
@@ -2,9 +2,12 @@
import com.baomidou.mybatisplus.extension.service.IService;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.utils.wx.pojo.AppletUserDecodeData;
import com.ruoyi.system.model.TTenant;
import com.ruoyi.system.query.TTenantQuery;
import com.ruoyi.system.vo.TenantVO;
import java.util.Map;
/**
 * <p>
@@ -22,4 +25,6 @@
     * @return
     */
    PageInfo<TenantVO> pageList(TTenantQuery query);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SysUserServiceImpl.java
@@ -6,8 +6,14 @@
import java.util.stream.Collectors;
import javax.validation.Validator;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.system.mapper.*;
import com.ruoyi.system.model.TDept;
import com.ruoyi.system.model.TDeptToUser;
import com.ruoyi.system.query.SysUserQuery;
import com.ruoyi.system.service.TDeptToUserService;
import com.ruoyi.system.vo.SysUserVO;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -27,11 +33,6 @@
import com.ruoyi.system.domain.SysPost;
import com.ruoyi.system.domain.SysUserPost;
import com.ruoyi.system.domain.SysUserRole;
import com.ruoyi.system.mapper.SysPostMapper;
import com.ruoyi.system.mapper.SysRoleMapper;
import com.ruoyi.system.mapper.SysUserMapper;
import com.ruoyi.system.mapper.SysUserPostMapper;
import com.ruoyi.system.mapper.SysUserRoleMapper;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
@@ -59,9 +60,13 @@
    @Autowired
    private SysUserPostMapper userPostMapper;
    @Autowired
    private TDeptToUserMapper deptToUserMapper;
    @Autowired
    private ISysConfigService configService;
    @Autowired
    private TDeptMapper deptMapper;
    @Autowired
    protected Validator validator;
@@ -264,7 +269,7 @@
        // 新增用户信息
        int rows = userMapper.insertUser(user);
        // 新增用户岗位关联
//        insertUserPost(user);
        insertUserDept(user);
        // 新增用户与角色管理
        insertUserRoleId(user);
        return rows;
@@ -297,10 +302,10 @@
        userRoleMapper.deleteUserRoleByUserId(userId);
        // 新增用户与角色管理
        insertUserRoleId(user);
        // 删除用户与岗位关联
//        userPostMapper.deleteUserPostByUserId(userId);
        // 新增用户与岗位管理
//        insertUserPost(user);
        // 删除用户与部门关联
        deptToUserMapper.deleteUserDeptByUserId(userId);
        // 新增用户与部门管理
        insertUserDept(user);
        return userMapper.updateUser(user);
    }
@@ -401,6 +406,17 @@
    }
    /**
     * 新增用户角色信息
     *
     * @param user 用户对象
     */
    public void insertUserDept(SysUser user)
    {
        this.insertUserDept(user.getUserId(), user.getDeptIds());
    }
    /**
     * 新增用户岗位信息
     * 
     * @param user 用户对象
@@ -460,6 +476,26 @@
            ur.setUserId(userId);
            ur.setRoleId(roleId);
            userRoleMapper.insertUserRole(ur);
        }
    }
    /**
     * 新增用户部门信息
     *
     * @param userId 用户ID
     * @param deptIds 部门id集合
     */
    public void insertUserDept(Long userId, List<String> deptIds)
    {
        if (Objects.nonNull(userId) && !CollectionUtils.isEmpty(deptIds)){
            List<TDeptToUser> deptToUserList = new ArrayList<>();
            for (String deptId : deptIds) {
                // 新增用户与角色管理
                TDeptToUser deptToUser = new TDeptToUser();
                deptToUser.setUserId(userId);
                deptToUser.setDeptId(deptId);
                deptToUserList.add(deptToUser);
            }
            userRoleMapper.insertBatchUserDept(deptToUserList);
        }
    }
@@ -628,6 +664,19 @@
    public PageInfo<SysUserVO> pageList(SysUserQuery query) {
        PageInfo<SysUserVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<SysUserVO> list = userMapper.pageList(query,pageInfo);
        if(CollectionUtils.isEmpty(list)){
            return pageInfo;
        }
        List<Long> userIds = list.stream().map(SysUserVO::getUserId).collect(Collectors.toList());
        // 查询所有部门
        List<TDept> depts = deptMapper.selectList(Wrappers.lambdaQuery(TDept.class));
        List<TDeptToUser> tDeptToUsers = deptToUserMapper.selectList(Wrappers.lambdaQuery(TDeptToUser.class)
                .in(TDeptToUser::getUserId, userIds));
        for (SysUserVO sysUserVO : list) {
            tDeptToUsers.stream().filter(tDeptToUser -> tDeptToUser.getUserId().equals(sysUserVO.getUserId())).forEach(tDeptToUser -> {
                sysUserVO.setDeptList(depts.stream().filter(tDept -> tDept.getId().equals(tDeptToUser.getDeptId())).map(TDept::getDeptName).collect(Collectors.toList()));
            });
        }
        pageInfo.setRecords(list);
        return pageInfo;
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TCheckAcceptRecordServiceImpl.java
@@ -1,10 +1,18 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.constant.DictConstants;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.system.mapper.TCheckAcceptRecordMapper;
import com.ruoyi.system.model.TCheckAcceptRecord;
import com.ruoyi.system.query.TCheckAcceptRecordQuery;
import com.ruoyi.system.service.TCheckAcceptRecordService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.SysUserVO;
import com.ruoyi.system.vo.TCheckAcceptRecordVO;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
@@ -17,4 +25,17 @@
@Service
public class TCheckAcceptRecordServiceImpl extends ServiceImpl<TCheckAcceptRecordMapper, TCheckAcceptRecord> implements TCheckAcceptRecordService {
    @Override
    public PageInfo<TCheckAcceptRecordVO> pageList(TCheckAcceptRecordQuery query) {
        PageInfo<TCheckAcceptRecordVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<TCheckAcceptRecordVO> list = this.baseMapper.pageList(query,pageInfo);
        list.forEach(item -> {
            item.setCleanSituation(DictUtils.getDictLabel(DictConstants.DICT_TYPE_CHECK_SITUATION,item.getCleanSituation()));
            item.setOverallSituation(DictUtils.getDictLabel(DictConstants.DICT_TYPE_CHECK_SITUATION,item.getOverallSituation()));
            item.setDeviceSituation(DictUtils.getDictLabel(DictConstants.DICT_TYPE_CHECK_SITUATION,item.getDeviceSituation()));
            item.setFurnitureSituation(DictUtils.getDictLabel(DictConstants.DICT_TYPE_CHECK_SITUATION,item.getFurnitureSituation()));
        });
        pageInfo.setRecords(list);
        return pageInfo;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TContractServiceImpl.java
@@ -17,16 +17,21 @@
import com.ruoyi.system.query.TContractBillQuery;
import com.ruoyi.system.query.TContractQuery;
import com.ruoyi.system.service.TBillService;
import com.ruoyi.system.service.TContractRentTypeService;
import com.ruoyi.system.service.TContractService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.BillVO;
import com.ruoyi.system.vo.CheckAcceptRecordVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -118,6 +123,7 @@
        CheckAcceptRecordVO checkAcceptRecordVO = new CheckAcceptRecordVO();
        TCheckAcceptRecord tCheckAcceptRecord = checkAcceptRecordMapper.selectOne(new LambdaQueryWrapper<TCheckAcceptRecord>()
                .eq(TCheckAcceptRecord::getContractId, id));
        if (tCheckAcceptRecord==null)return checkAcceptRecordVO;
        BeanUtils.copyProperties(tCheckAcceptRecord,checkAcceptRecordVO);
        THouse tHouse = houseMapper.selectById(tCheckAcceptRecord.getHouseId());
        tHouse.setLeaseStatus(DictUtils.getDictLabel(DictConstants.DICT_TYPE_LEASE_STATUS,tHouse.getLeaseStatus()));
@@ -125,7 +131,8 @@
        checkAcceptRecordVO.setHouse(tHouse);
        return checkAcceptRecordVO;
    }
    @Autowired
    private TContractRentTypeService contractRentTypeService;
    @Override
    public R signContract(SignContractDTO dto) {
        TContract contract = contractMapper.selectById(dto.getId());
@@ -134,42 +141,475 @@
        contract.setSignature(dto.getSignature());
        contract.setStatus("2");
        contractMapper.updateById(contract);
        // 用户签订合同后 生成第一批账单包含租金账单和押金账单 后续账单通过定时任务生成
        TBill rent = new TBill();
        rent.setContractId(contract.getId());
        // 应缴费租金
        BigDecimal payableFeesMoney = new BigDecimal("0");
        LocalDateTime startTime = contract.getStartTime();
        LocalDateTime endTime = contract.getEndTime();
//        // 计算相差多少天
//        long days = ChronoUnit.DAYS.between(startTime, endTime);
//        // 计算相差多少个月
//        long months = ChronoUnit.MONTHS.between(startTime, endTime);
//        if (months<=31){
//            // 小于等于一个月 合计租金就是首笔账单金额
//            payableFeesMoney = contract.getTotalRent();
//        }else{
//            switch (contract.getPayType()){
//                case "1":
//                    break;
//                case "2":
//                    break;
//                case "3":
//                    break;
//            }
//        }
//
//        rent.setPayableFeesMoney(payableFeesMoney);
//        rent.setPayableFeesTime(contract.getFirstPayTime());
//        rent.setPayFeesStatus("1");
//        rent.setBillType("1");
//
//        TBill deposit = new TBill();
//        deposit.setContractId(contract.getId());
//        deposit.setPayableFeesMoney(contract.getDeposit());
//        deposit.setPayableFeesTime(contract.getFirstPayTime());
//        deposit.setPayFeesStatus("1");
//        deposit.setBillType("2");
        contract.setFirstPayTime(contract.getStartTime().plusDays(10));
        List<TBill> bills = new ArrayList<>();
        List<TContractRentType> contractRentTypes = contractRentTypeService.list();
        // 生成第一笔账单
        // 第一次应缴费日期
        LocalDateTime firstPayTime = contract.getStartTime().plusDays(10).withHour(0).withMinute(0).withSecond(0);
        TBill rentBill = new TBill();
        rentBill.setContractId(contract.getId());
        rentBill.setContractNumber(contract.getContractNumber());
        rentBill.setPayableFeesTime(firstPayTime);
        if (firstPayTime.toLocalDate().equals(LocalDate.now())){
            rentBill.setPayFeesStatus("1");
        }else {
            rentBill.setPayFeesStatus("2");
        }
        rentBill.setBillType("1");
        rentBill.setStartTime(contract.getStartPayTime());
        TContractRentType tContractRentType = contractRentTypes.stream().filter(e -> e.getContractId().equals(contract.getId())).findFirst().orElse(null);
        if (tContractRentType!=null && contract.getStartPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(tContractRentType.getChangeTime())){
            // 计算租金变动的天数
            long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getStartPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12))+1L;
            contract.setChangeTime(LocalDateTime.now());
            // 递增递减的租金
            BigDecimal contractRentTypeMoney = new BigDecimal("0");
            // 不递增递减的租金
            BigDecimal originalMoney = new BigDecimal("0");
            // 原租金
            switch (tContractRentType.getIncreasingDecreasingType()){
                case 1:
                    switch (tContractRentType.getIncreasingDecreasing()){
                        case 1:
                            contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                            contract.setChangeRent(contractRentTypeMoney);
                            break;
                        case 2:
                            contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                            contract.setChangeRent(contractRentTypeMoney);
                            break;
                    }
                    break;
                case 2:
                    switch (tContractRentType.getIncreasingDecreasing()){
                        case 1:
                            contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                            contract.setChangeRent(contractRentTypeMoney);
                            break;
                        case 2:
                            contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                            contract.setChangeRent(contractRentTypeMoney);
                            break;
                    }
                    break;
            }
            // 不需要涨租金的时间段
            long originalDays = ChronoUnit.DAYS.between(contract.getFirstPayTime(), tContractRentType.getChangeTime());
            originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                    .multiply(new BigDecimal(originalDays));
            rentBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
            rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney());
            if (contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(contract.getEndTime())){
                rentBill.setEndTime(contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
            }else{
                rentBill.setEndTime(contract.getEndTime());
            }
        }else{
            if (contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isAfter(contract.getEndTime())){
                rentBill.setEndTime(contract.getFirstPayTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
            }else{
                rentBill.setEndTime(contract.getEndTime());
            }
            // 不走递增递减
            long allDays = ChronoUnit.DAYS.between(contract.getFirstPayTime(), rentBill.getEndTime());
            rentBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
            rentBill.setOutstandingMoney(rentBill.getPayableFeesMoney());
        }
        // 押金账单
        TBill depositBill = new TBill();
        depositBill.setContractId(contract.getId());
        depositBill.setContractNumber(contract.getContractNumber());
        depositBill.setPayableFeesMoney(contract.getDeposit());
        depositBill.setOutstandingMoney(depositBill.getPayableFeesMoney());
        depositBill.setStartTime(contract.getStartPayTime());
        depositBill.setEndTime(contract.getEndTime());
        depositBill.setPayableFeesTime(firstPayTime);
        if (firstPayTime.toLocalDate().equals(LocalDate.now())){
            depositBill.setPayFeesStatus("1");
        }else {
            depositBill.setPayFeesStatus("2");
        }
        depositBill.setBillType("2");
        this.updateById(contract);
        billService.save(rentBill);
        billService.save(depositBill);
        // 生成后续账单
        try {
            TBill beforeBill = billService.lambdaQuery().eq(TBill::getContractId, contract.getId()).eq(TBill::getBillType, 1).orderByDesc(TBill::getCreateTime)
                    .last("limit 1").one();
            if (!beforeBill.getEndTime().toLocalDate().equals(contract.getEndTime().toLocalDate())){
            while(beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).isBefore(contract.getEndTime())){
                    TBill tBill = new TBill();
                    tBill.setContractId(contract.getId());
                    tBill.setContractNumber(contract.getContractNumber());
                    // 根据支付方式判断需不需要生成订单
                    if (!(beforeBill.getEndTime().toLocalDate().equals(contract.getEndTime().toLocalDate()))
                            &&
                            (contract.getPayType().equals("1")?
                                    beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                                    beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12))
                                    .with(TemporalAdjusters.lastDayOfMonth()).isBefore(contract.getEndTime())
                            && beforeBill.getEndTime().isBefore(contract.getEndTime())
                    ){
                        tBill.setContractId(contract.getId());
                        if (contract.getIsIncreasing()){
                            if (tContractRentType!=null
                                    && beforeBill.getEndTime().isBefore(tContractRentType.getChangeTime())
                                    && beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).with(TemporalAdjusters.lastDayOfMonth()).isAfter(tContractRentType.getChangeTime())){
                                // 如果没变过
                                if (contract.getChangeTime()==null){
                                    contract.setChangeTime(LocalDateTime.now());
                                    // 租金递增递减的时长 天
                                    long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                    // 递增递减的租金
                                    BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                    // 不递增递减的租金
                                    BigDecimal originalMoney = new BigDecimal("0");
                                    // 原租金
                                    switch (tContractRentType.getIncreasingDecreasingType()){
                                        case 1:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                                    contract.setChangeRent(contractRentTypeMoney);
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                                    contract.setChangeRent(contractRentTypeMoney);
                                                    break;
                                            }
                                            break;
                                        case 2:
                                            switch (tContractRentType.getIncreasingDecreasing()){
                                                case 1:
                                                    contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    contract.setChangeRent(contractRentTypeMoney);
                                                    break;
                                                case 2:
                                                    contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                    contract.setChangeRent(contractRentTypeMoney);
                                                    break;
                                            }
                                            break;
                                    }
                                    // 不需要涨租金的时间段
                                    long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                    originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                            .multiply(new BigDecimal(originalDays));
                                    tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                    tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                                }else{
                                    // 之前已经涨、跌过租金了 需要判断周期是否还需要再涨、跌
                                    if ((LocalDateTime.now().getYear() - contract.getChangeTime().getYear())%tContractRentType.getCycleTime()==0){
                                        contract.setChangeTime(LocalDateTime.now());
                                        // 租金递增递减的时长 天
                                        long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                        // 递增递减的租金
                                        BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                        // 不递增递减的租金
                                        BigDecimal originalMoney = new BigDecimal("0");
                                        // 原租金
                                        switch (tContractRentType.getIncreasingDecreasingType()){
                                            case 1:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                }
                                                break;
                                            case 2:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                }
                                                break;
                                        }
                                        // 不需要涨租金的时间段
                                        long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                        originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                                .multiply(new BigDecimal(originalDays));
                                        tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                        tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                                    }else{
                                        // 不涨租金 用上次的
                                        // 租金递增递减的时长 天
                                        long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                                        // 递增递减的租金
                                        BigDecimal contractRentTypeMoney = new BigDecimal("0");
                                        // 不递增递减的租金
                                        BigDecimal originalMoney = new BigDecimal("0");
                                        // 原租金
                                        switch (tContractRentType.getIncreasingDecreasingType()){
                                            case 1:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                }
                                                break;
                                            case 2:
                                                switch (tContractRentType.getIncreasingDecreasing()){
                                                    case 1:
                                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                    case 2:
                                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                                        contract.setChangeRent(contractRentTypeMoney);
                                                        break;
                                                }
                                                break;
                                        }
                                        // 不需要涨租金的时间段
                                        long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                                        originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                                .multiply(new BigDecimal(originalDays));
                                        tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                                        tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                                    }
                                }
                            }
                        }else{
                            long allDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), (contract.getPayType().equals("1")?
                                    beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                                    beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12)).with(TemporalAdjusters.lastDayOfMonth()));
                            tBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
                            tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                        }
                        tBill.setContractNumber(contract.getContractNumber());
                        if (beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).getDayOfMonth()<=15){
                            tBill.setPayableFeesTime(contract.getEndTime());
                        }else{
                            tBill.setPayableFeesTime((contract.getPayType().equals("1")?
                                    beforeBill.getEndTime().plusMonths(1).withDayOfMonth(15):contract.getPayType().equals("2")?
                                    beforeBill.getEndTime().plusMonths(3).withDayOfMonth(15):beforeBill.getEndTime().withDayOfMonth(15).plusMonths(12)));
                        }
                        tBill.setPayFeesStatus("2");
                        tBill.setBillType("1");
                        tBill.setStartTime(beforeBill.getEndTime().plusDays(1));
                        tBill.setEndTime(contract.getEndTime());
                    }
                    billMapper.insert(tBill);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        TBill beforeBill = billService.lambdaQuery().eq(TBill::getContractId, contract.getId()).eq(TBill::getBillType, 1).orderByDesc(TBill::getCreateTime)
                .last("limit 1").one();
        // 生成最后一笔账单
        if (!(beforeBill.getEndTime().toLocalDate().equals(contract.getEndTime().toLocalDate()))
                &&
                (contract.getPayType().equals("1")?
                        beforeBill.getEndTime().plusMonths(1):contract.getPayType().equals("2")?
                        beforeBill.getEndTime().plusMonths(3):beforeBill.getEndTime().plusMonths(12))
                        .with(TemporalAdjusters.lastDayOfMonth()).isAfter(contract.getEndTime())
                && beforeBill.getEndTime().isBefore(contract.getEndTime())
        ){
            TBill tBill = new TBill();
            tBill.setContractId(contract.getId());
            if (contract.getIsIncreasing()){
                if (tContractRentType!=null
                        && beforeBill.getEndTime().isBefore(tContractRentType.getChangeTime())
                        && beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).with(TemporalAdjusters.lastDayOfMonth()).isAfter(tContractRentType.getChangeTime())){
                    // 如果没变过
                    if (contract.getChangeTime()==null){
                        contract.setChangeTime(LocalDateTime.now());
                        // 租金递增递减的时长 天
                        long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                        // 递增递减的租金
                        BigDecimal contractRentTypeMoney = new BigDecimal("0");
                        // 不递增递减的租金
                        BigDecimal originalMoney = new BigDecimal("0");
                        // 原租金
                        switch (tContractRentType.getIncreasingDecreasingType()){
                            case 1:
                                switch (tContractRentType.getIncreasingDecreasing()){
                                    case 1:
                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                        contract.setChangeRent(contractRentTypeMoney);
                                        break;
                                    case 2:
                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                        contract.setChangeRent(contractRentTypeMoney);
                                        break;
                                }
                                break;
                            case 2:
                                switch (tContractRentType.getIncreasingDecreasing()){
                                    case 1:
                                        contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                        contract.setChangeRent(contractRentTypeMoney);
                                        break;
                                    case 2:
                                        contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                        contract.setChangeRent(contractRentTypeMoney);
                                        break;
                                }
                                break;
                        }
                        // 不需要涨租金的时间段
                        long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                        // 不需要涨租金的时间段
                        if (contract.getFirstPayTime().isBefore(tContractRentType.getChangeTime())){
                            originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                    .multiply(new BigDecimal(originalDays));
                            tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                            tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                        }else{
                            tBill.setPayableFeesMoney(contractRentTypeMoney);
                            tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                        }
                    }else{
                        // 之前已经涨、跌过租金了 需要判断周期是否还需要再涨、跌
                        if ((LocalDateTime.now().getYear() - contract.getChangeTime().getYear())%tContractRentType.getCycleTime()==0){
                            contract.setChangeTime(LocalDateTime.now());
                            // 租金递增递减的时长 天
                            long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                            // 递增递减的租金
                            BigDecimal contractRentTypeMoney = new BigDecimal("0");
                            // 不递增递减的租金
                            BigDecimal originalMoney = new BigDecimal("0");
                            // 原租金
                            switch (tContractRentType.getIncreasingDecreasingType()){
                                case 1:
                                    switch (tContractRentType.getIncreasingDecreasing()){
                                        case 1:
                                            contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                        case 2:
                                            contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                    }
                                    break;
                                case 2:
                                    switch (tContractRentType.getIncreasingDecreasing()){
                                        case 1:
                                            contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                        case 2:
                                            contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                    }
                                    break;
                            }
                            // 不需要涨租金的时间段
                            long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                            originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                    .multiply(new BigDecimal(originalDays));
                            tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                            tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                        }else{
                            // 不涨租金 用上次的
                            // 租金递增递减的时长 天
                            long moneyDays = ChronoUnit.DAYS.between(tContractRentType.getChangeTime(), contract.getEndTime());
                            // 递增递减的租金
                            BigDecimal contractRentTypeMoney = new BigDecimal("0");
                            // 不递增递减的租金
                            BigDecimal originalMoney = new BigDecimal("0");
                            // 原租金
                            switch (tContractRentType.getIncreasingDecreasingType()){
                                case 1:
                                    switch (tContractRentType.getIncreasingDecreasing()){
                                        case 1:
                                            contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().multiply(new BigDecimal(100).add(tContractRentType.getNumericalValue())).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                        case 2:
                                            contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().multiply((new BigDecimal(100).subtract(tContractRentType.getNumericalValue()))).divide(new BigDecimal(100),2,BigDecimal.ROUND_DOWN).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays)).divide(new BigDecimal(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12),2,BigDecimal.ROUND_DOWN));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                    }
                                    break;
                                case 2:
                                    switch (tContractRentType.getIncreasingDecreasing()){
                                        case 1:
                                            contractRentTypeMoney =contractRentTypeMoney.add(contract.getChangeRent().add(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                        case 2:
                                            contractRentTypeMoney = contractRentTypeMoney.add(contract.getChangeRent().subtract(tContractRentType.getNumericalValue())).divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(moneyDays));
                                            contract.setChangeRent(contractRentTypeMoney);
                                            break;
                                    }
                                    break;
                            }
                            // 不需要涨租金的时间段
                            long originalDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), tContractRentType.getChangeTime());
                            originalMoney=originalMoney.add(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN))
                                    .multiply(new BigDecimal(originalDays));
                            tBill.setPayableFeesMoney(contractRentTypeMoney.add(originalMoney));
                            tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
                        }
                    }
                }
            }else{
                long allDays = ChronoUnit.DAYS.between(beforeBill.getEndTime(), contract.getEndTime());
                tBill.setPayableFeesMoney(contract.getMonthRent().divide(new BigDecimal(30), 2, BigDecimal.ROUND_DOWN).multiply(new BigDecimal(allDays)));
                tBill.setOutstandingMoney(tBill.getPayableFeesMoney());
            }
            tBill.setContractNumber(contract.getContractNumber());
            if (beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12).getDayOfMonth()<=15){
                tBill.setPayableFeesTime(contract.getEndTime().withHour(0).withMinute(0).withSecond(0));
            }else{
                tBill.setPayableFeesTime((contract.getPayType().equals("1")?
                        beforeBill.getEndTime().plusMonths(1).withDayOfMonth(15):contract.getPayType().equals("2")?
                        beforeBill.getEndTime().plusMonths(3).withDayOfMonth(15):beforeBill.getEndTime().withDayOfMonth(15).plusMonths(12).withHour(0).withMinute(0).withSecond(0)));
            }
            tBill.setPayFeesStatus("1");
            tBill.setBillType("1");
            tBill.setStartTime(beforeBill.getEndTime().plusDays(1));
            tBill.setEndTime(beforeBill.getEndTime().plusMonths(contract.getPayType().equals("1")? 1:contract.getPayType().equals("2")? 3:12));
            billService.save(tBill);
        }
        return R.ok();
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TFaultAreaDicServiceImpl.java
@@ -11,6 +11,7 @@
import com.ruoyi.system.query.TFaultAreaDicQuery;
import com.ruoyi.system.service.TFaultAreaDicService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.TFaultAreaDicVO;
import com.ruoyi.system.vo.TenantVO;
import org.springframework.stereotype.Service;
@@ -45,4 +46,9 @@
            return this.count(Wrappers.lambdaQuery(TFaultAreaDic.class).eq(TFaultAreaDic::getFaultAreaName, dto.getFaultAreaName())) > 0;
        }
    }
    @Override
    public List<TFaultAreaDicVO> getAreaDicList() {
        return this.baseMapper.getAreaDicList();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TFaultRepairMessageServiceImpl.java
@@ -1,10 +1,16 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.system.mapper.TFaultRepairMessageMapper;
import com.ruoyi.system.model.TFaultRepairMessage;
import com.ruoyi.system.query.TFaultRepairMessageQuery;
import com.ruoyi.system.service.TFaultRepairMessageService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.SysOperLogVO;
import com.ruoyi.system.vo.TFaultRepairMessageVO;
import org.springframework.stereotype.Service;
import java.util.List;
/**
 * <p>
@@ -17,4 +23,16 @@
@Service
public class TFaultRepairMessageServiceImpl extends ServiceImpl<TFaultRepairMessageMapper, TFaultRepairMessage> implements TFaultRepairMessageService {
    @Override
    public TFaultRepairMessageVO getDetailById(String id) {
        return this.baseMapper.getDetailById(id);
    }
    @Override
    public PageInfo<TFaultRepairMessageVO> pageList(TFaultRepairMessageQuery query) {
        PageInfo<TFaultRepairMessageVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<TFaultRepairMessageVO> list = this.baseMapper.pageList(query,pageInfo);
        pageInfo.setRecords(list);
        return pageInfo;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TInformationServiceImpl.java
@@ -1,12 +1,15 @@
package com.ruoyi.system.service.impl;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.system.mapper.TInformationMapper;
import com.ruoyi.system.model.TInformation;
import com.ruoyi.system.query.TInformationQuery;
import com.ruoyi.system.service.TInformationService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.HouseVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -22,10 +25,16 @@
@Service
public class TInformationServiceImpl extends ServiceImpl<TInformationMapper, TInformation> implements TInformationService {
    @Autowired
    private RedisCache redisCache;
    @Override
    public PageInfo<TInformation> pageList(TInformationQuery query) {
        PageInfo<TInformation> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<TInformation> list = this.baseMapper.pageList(query,pageInfo);
        list.forEach(item -> {
            item.setViewCount(redisCache.getCacheObject(Constants.INFORMATION_VIEW + item.getId()));
        });
        pageInfo.setRecords(list);
        return pageInfo;
    }
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TItemTypeServiceImpl.java
@@ -10,6 +10,7 @@
import com.ruoyi.system.query.TItemTypeQuery;
import com.ruoyi.system.service.TItemTypeService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.TItemTypeVO;
import org.springframework.stereotype.Service;
import java.util.List;
@@ -43,4 +44,9 @@
        pageInfo.setRecords(list);
        return pageInfo;
    }
    @Override
    public List<TItemTypeVO> getItemList() {
        return this.baseMapper.getItemList();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/TTenantServiceImpl.java
@@ -1,18 +1,20 @@
package com.ruoyi.system.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.basic.PageInfo;
import com.ruoyi.common.constant.DictConstants;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.TTenantMapper;
import com.ruoyi.system.model.TTenant;
import com.ruoyi.system.query.TTenantQuery;
import com.ruoyi.system.service.TTenantService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.system.vo.SysUserVO;
import com.ruoyi.system.utils.wx.pojo.AppletUserDecodeData;
import com.ruoyi.system.vo.TenantVO;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
 * <p>
@@ -30,10 +32,11 @@
        PageInfo<TenantVO> pageInfo = new PageInfo<>(query.getPageNum(), query.getPageSize());
        List<TenantVO> list = this.baseMapper.pageList(query,pageInfo);
        for (TenantVO tenantVO : list) {
            tenantVO.setTenantAttributesName(DictUtils.getDictLabel(DictConstants.DICT_TYPE_TENANT_ATTRIBUTE,tenantVO.getTenantAttributes()));
            tenantVO.setTenantTypeName(DictUtils.getDictLabel(DictConstants.DICT_TYPE_TENANT_TYPE,tenantVO.getTenantType()));
            tenantVO.setTenantAttributesName(StringUtils.isNotBlank(tenantVO.getTenantAttributes())?DictUtils.getDictLabel(DictConstants.DICT_TYPE_TENANT_ATTRIBUTE,tenantVO.getTenantAttributes()):"");
            tenantVO.setTenantTypeName(StringUtils.isNotBlank(tenantVO.getTenantType())?DictUtils.getDictLabel(DictConstants.DICT_TYPE_TENANT_TYPE,tenantVO.getTenantType()):"");
        }
        pageInfo.setRecords(list);
        return pageInfo;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/AccessTokenRespBody.java
New file
@@ -0,0 +1,28 @@
package com.ruoyi.system.utils.wx.body.resp;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.io.Serializable;
/**
 * AccessToken 全局唯一
 *
 * @author xiaochen
 */
@Data
public class AccessTokenRespBody extends RespBody implements Serializable {
    /**
     * 获取到的凭证
     */
    @JsonProperty("access_token")
    private String accessToken;
    /**
     * 凭证有效时间,单位:秒
     */
    @JsonProperty("expires_in")
    private int expiresIn;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/Code2SessionRespBody.java
New file
@@ -0,0 +1,29 @@
package com.ruoyi.system.utils.wx.body.resp;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
 * @author xiaochen
 * @ClassName Code2SessionRespBody
 * @Description
 * @date 2021-07-28 12:35
 */
@Data
public class Code2SessionRespBody extends RespBody {
    /**
     * 用户唯一标识
     */
    @JsonProperty("openid")
    private String openid;
    /**
     * 会话密钥
     */
    @JsonProperty("session_key")
    private String sessionKey;
    /**
     * 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。
     */
    @JsonProperty("unionid")
    private String unionid;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resp/RespBody.java
New file
@@ -0,0 +1,19 @@
package com.ruoyi.system.utils.wx.body.resp;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
 * @author xiaochen
 * @ClassName RespBody
 * @Description
 * @date 2021-07-28 11:44
 */
@Data
public class RespBody {
    @JsonProperty("errcode")
    private Integer errorCode;
    @JsonProperty("errmsg")
    private String errorMsg;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/body/resq/Code2SessionResqBody.java
New file
@@ -0,0 +1,21 @@
package com.ruoyi.system.utils.wx.body.resq;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
 * @author xiaochen
 * @ClassName Code2SessionResqBody
 * @Description
 * @date 2021-07-28 11:47
 */
@Data
public class Code2SessionResqBody {
    @JsonProperty("js_code")
    private String jsCode;
    public Code2SessionResqBody build(String jsCode) {
        this.jsCode = jsCode;
        return this;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/model/WeixinProperties.java
New file
@@ -0,0 +1,79 @@
package com.ruoyi.system.utils.wx.model;
import lombok.ToString;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * @author xiaochen
 * @ClassName WeixinProperties
 * @Description
 * @date 2024-08-14 13:55
 */
@ToString
@Component
@ConfigurationProperties(prefix = "wx.conf")
public class WeixinProperties {
    /**
     * 默认开启
     */
    private boolean enabled = true;
    /**
     * 获取 App ID
     *
     * @return App ID
     */
    private String appId;
    /**
     * 获取 Mch ID
     *
     * @return Mch ID
     */
    private String mchId;
    /**
     * 获取 secret ID
     *
     * @return secret ID
     */
    private String secretId;
    public String getSecretId() {
        return secretId;
    }
    public void setSecretId(String secretId) {
        this.secretId = secretId;
    }
    /**
     * HTTP(S) 连接超时时间,单位毫秒
     *
     */
    public int getHttpConnectTimeoutMs() {
        return 6 * 1000;
    }
    /**
     * HTTP(S) 读数据超时时间,单位毫秒
     */
    public int getHttpReadTimeoutMs() {
        return 8 * 1000;
    }
    public String getAppId() {
        return appId;
    }
    public void setAppId(String appId) {
        this.appId = appId;
    }
    public String getMchId() {
        return mchId;
    }
    public void setMchId(String mchId) {
        this.mchId = mchId;
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletPhoneEncrypteData.java
New file
@@ -0,0 +1,19 @@
package com.ruoyi.system.utils.wx.pojo;
import lombok.Data;
/**
 * @author xiaochen
 * @ClassName AppletUserDecodeData
 * @Description
 * @date 2021-08-13 17:46
 * 小程序加密数据体
 *
 */
@Data
public class AppletPhoneEncrypteData {
    private String encryptedData;
    private String openid;
    private String unionid;
    private String iv;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserDecodeData.java
New file
@@ -0,0 +1,52 @@
package com.ruoyi.system.utils.wx.pojo;
import lombok.Data;
/**
 * @author xiaochen
 * @ClassName AppletUserDecodeData
 * @Description
 * 用户主体信息部分
 * {
 *     "openId": "OPENID",
 *     "nickName": "NICKNAME",
 *     "gender": GENDER,
 *     "city": "CITY",
 *     "province": "PROVINCE",
 *     "country": "COUNTRY",
 *     "avatarUrl": "AVATARURL",
 *     "unionId": "UNIONID",
 *     "watermark":
 *     {
 *         "appid":"APPID",
 *         "timestamp":TIMESTAMP
 *     }
 * }
 * 电话部分
 * {
 *     "phoneNumber": "13580006666",
 *     "purePhoneNumber": "13580006666",
 *     "countryCode": "86",
 *     "watermark":
 *     {
 *         "appid":"APPID",
 *         "timestamp": TIMESTAMP
 *     }
 * }
 *
 */
@Data
public class AppletUserDecodeData {
    private String openId;
    private String unionId;
    private String nickName;
    private int gender;
    private String city;
    private String province;
    private String country;
    private String avatarUrl;
    private Watermark watermark;
    private String phoneNumber;
    private String purePhoneNumber;
    private String countryCode;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/AppletUserEncrypteData.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.system.utils.wx.pojo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
 * @author xiaochen
 * @ClassName AppletUserDecodeData
 * @Description
 * 小程序加密数据体
 *
 */
@Data
public class AppletUserEncrypteData extends AppletPhoneEncrypteData {
    private String rawData;
    private String signature;
    private String code;
    @ApiModelProperty(value = "邀请用户id")
    private Long inviteUserId;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/pojo/Watermark.java
New file
@@ -0,0 +1,9 @@
package com.ruoyi.system.utils.wx.pojo;
import lombok.Data;
@Data
public class Watermark {
    private String appid;
    private String timestamp;
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/SHA1.java
New file
@@ -0,0 +1,36 @@
package com.ruoyi.system.utils.wx.tools;
import java.security.MessageDigest;
public class SHA1 {
    /**
     * 用SHA1算法生成安全签名
     *
     * @param str
     * @return
     * @throws WxException
     */
    public static String getSHA1(String str) throws WxException {
        try {
            // SHA1签名生成
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            md.update(str.getBytes());
            byte[] digest = md.digest();
            StringBuffer hexstr = new StringBuffer();
            String shaHex;
            for (int i = 0; i < digest.length; i++) {
                shaHex = Integer.toHexString(digest[i] & 0xFF);
                if (shaHex.length() < 2) {
                    hexstr.append(0);
                }
                hexstr.append(shaHex);
            }
            return hexstr.toString();
        } catch (Exception e) {
            throw new WxException(WxException.ComputeSignatureError);
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WebUtils.java
New file
@@ -0,0 +1,48 @@
package com.ruoyi.system.utils.wx.tools;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
 * @Author xiaochen
 * @Date 2019/08/26 10:28 AM
 * @Description
 */
public final class WebUtils {
    private WebUtils() {
    }
    /**
     * 当前请求
     */
    public static HttpServletRequest request() {
        return contextHolder() == null ? null : contextHolder().getRequest();
    }
    /**
     * 当前响应
     */
    public static HttpServletResponse response() {
        return contextHolder() == null ? null : contextHolder().getResponse();
    }
    /**
     * 当前session
     */
    public static HttpSession session() {
        return request() == null ? null : request().getSession();
    }
    /**
     * 当前ServletRequest
     */
    public static ServletRequestAttributes contextHolder() {
        return (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxAppletTools.java
New file
@@ -0,0 +1,123 @@
package com.ruoyi.system.utils.wx.tools;
import com.ruoyi.common.redis.service.RedisService;
import com.ruoyi.system.utils.wx.body.resp.AccessTokenRespBody;
import com.ruoyi.system.utils.wx.body.resp.Code2SessionRespBody;
import com.ruoyi.system.utils.wx.body.resq.Code2SessionResqBody;
import com.ruoyi.system.utils.wx.model.WeixinProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import java.text.MessageFormat;
import java.util.concurrent.TimeUnit;
/**
 * @author xiaochen
 * @ClassName WxAppletTools
 * @Description
 * @date 2024-8-04 13:55
 */
@Slf4j
public class WxAppletTools {
    private final static String ACCESSTOKEN_CACHE_KEY = "accessToken";
    /**
     * 请求参数
     * 属性    类型    默认值    必填    说明
     * appid    string        是    小程序 appId
     * secret    string        是    小程序 appSecret
     * js_code    string        是    登录时获取的 code
     * grant_type    string        是    授权类型,此处只需填写 authorization_cod
     * <p>
     * 返回值:
     * <p>
     * 属性    类型    说明
     * openid    string    用户唯一标识
     * session_key    string    会话密钥
     * unionid    string    用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。
     * errcode    number    错误码
     * errmsg    string    错误信息
     */
    private static final String JSCODE_2_SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid={0}&secret={1}&js_code={2}&grant_type=authorization_code";
    /**
     * 请求参数
     * 属性    类型    默认值    必填    说明
     * grant_type    string        是    填写 client_credential
     * appid    string        是    小程序唯一凭证,即 AppID,可在「微信公众平台 - 设置 - 开发设置」页中获得。(需要已经成为开发者,且帐号没有异常状态)
     * secret    string        是    小程序唯一凭证密钥,即 AppSecret,获取方式同 appid
     * 返回值
     * Object
     * 返回的 JSON 数据包
     * <p>
     * 属性    类型    说明
     * access_token    string    获取到的凭证
     * expires_in    number    凭证有效时间,单位:秒。目前是7200秒之内的值。
     * errcode    number    错误码
     * errmsg    string    错误信息
     */
    public static String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={0}&secret={1}";
    private WeixinProperties wxConfig;
    private RestTemplate wxRestTemplate;
    private RedisService redisService;
    public WxAppletTools(RestTemplate wxRestTemplate, WeixinProperties wxConfig, RedisService redisService) {
        this.wxRestTemplate = wxRestTemplate;
        this.wxConfig = wxConfig;
        this.redisService = redisService;
    }
    /**
     * 自定义部分数据
     *
     * @param wxConfig
     * @return
     */
    public WxAppletTools build(WeixinProperties wxConfig) {
        this.wxConfig = wxConfig;
        return this;
    }
    /**
     * @param resqBody
     * @return
     */
    public Code2SessionRespBody getOpenIdByJscode2session(Code2SessionResqBody resqBody) {
        long start = System.currentTimeMillis();
        String requestUrl = MessageFormat.format(JSCODE_2_SESSION_URL, wxConfig.getAppId(), wxConfig.getSecretId(), resqBody.getJsCode());
        long end = System.currentTimeMillis();
        log.info("code换取sessionKey时间:{}", (end - start));
        String respBody = wxRestTemplate.getForEntity(requestUrl, String.class).getBody();
        end = System.currentTimeMillis();
        log.info("code换取sessionKey时间:{}", (end - start));
        log.info("Jscode2session:{}", respBody);
        Code2SessionRespBody code2SessionRespBody = WxJsonUtils.parseObject(respBody, Code2SessionRespBody.class);
        // 判断有误异常
        if (StringUtils.hasLength(code2SessionRespBody.getErrorMsg())) {
            // 抛出错误
            throw new WxException(code2SessionRespBody.getErrorCode() + ":" + code2SessionRespBody.getErrorMsg());
        }
        return code2SessionRespBody;
    }
    /**
     * @return
     */
    public String getAccessToken(String version) {
        String accessToken = redisService.getCacheObject(ACCESSTOKEN_CACHE_KEY + version);
        if (StringUtils.hasLength(accessToken)) {
            return accessToken;
        }
        String requestUrl = MessageFormat.format(ACCESS_TOKEN_URL, wxConfig.getAppId(), wxConfig.getSecretId());
        String respBody = wxRestTemplate.getForEntity(requestUrl, String.class).getBody();
        AccessTokenRespBody accessTokenRespBody = WxJsonUtils.parseObject(respBody, AccessTokenRespBody.class);
        // 判断有误异常
        if (StringUtils.hasLength(accessTokenRespBody.getErrorMsg())) {
            // 抛出错误
            throw new WxException(accessTokenRespBody.getErrorCode() + ":" + accessTokenRespBody.getErrorMsg());
        }
        redisService.setCacheObject(ACCESSTOKEN_CACHE_KEY + version, accessTokenRespBody.getAccessToken(), 7200L, TimeUnit.SECONDS);
        return accessTokenRespBody.getAccessToken();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCache.java
New file
@@ -0,0 +1,117 @@
package com.ruoyi.system.utils.wx.tools;
import java.util.concurrent.TimeUnit;
/**
 * 缓存
 *
 * @author xiaochen
 */
class WxCache {
    /**
     * 缓存的初始化容量
     */
    private int initialCapacity = 50;
    /**
     * 缓存最大容量
     */
    private long maximumSize = 200L;
    /**
     * 缓存时长
     */
    private long duration = 7000L;
    /**
     * 时长单位,自动转换
     * 支持:
     * 时
     * 分
     * 秒
     * 天
     */
    private TimeUnit timeunit = TimeUnit.SECONDS;
    public int getInitialCapacity() {
        return initialCapacity;
    }
    public void setInitialCapacity(int initialCapacity) {
        this.initialCapacity = initialCapacity;
    }
    public long getMaximumSize() {
        return maximumSize;
    }
    public void setMaximumSize(long maximumSize) {
        this.maximumSize = maximumSize;
    }
    public long getDuration() {
        return duration;
    }
    public void setDuration(long duration) {
        this.duration = duration;
    }
    public TimeUnit getTimeunit() {
        return timeunit;
    }
    public void setTimeunit(TimeUnit timeunit) {
        this.timeunit = timeunit;
    }
    public static class Builder {
        private int initialCapacity;
        private long maximumSize;
        private long duration;
        private TimeUnit timeunit;
        public Builder setInitialCapacity(int initialCapacity) {
            this.initialCapacity = initialCapacity;
            return this;
        }
        public Builder setMaximumSize(long maximumSize) {
            this.maximumSize = maximumSize;
            return this;
        }
        public Builder setDuration(long duration) {
            this.duration = duration;
            return this;
        }
        public Builder setTimeUnit(TimeUnit timeunit) {
            this.timeunit = timeunit;
            return this;
        }
        public WxCache build() {
            return new WxCache(this);
        }
    }
    public static Builder options() {
        return new Builder();
    }
    private WxCache(Builder builder) {
        this.initialCapacity = 0 == builder.initialCapacity ? this.initialCapacity : builder.initialCapacity;
        this.maximumSize = 0L == builder.maximumSize ? this.maximumSize : builder.maximumSize;
        this.duration = 0L == builder.duration ? this.duration : builder.duration;
        this.timeunit = null == builder.timeunit ? this.timeunit : builder.timeunit;
    }
    @Override
    public String toString() {
        return "WxCache{" +
                "initialCapacity=" + initialCapacity +
                ", maximumSize=" + maximumSize +
                ", duration=" + duration +
                ", timeunit=" + timeunit +
                '}';
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxCacheTemplate.java
New file
@@ -0,0 +1,34 @@
package com.ruoyi.system.utils.wx.tools;
/**
 * @author xiaochen
 * @ClassName WxCacheTemplate
 * @Description
 * @date 2021-01-11 11:27
 */
public interface WxCacheTemplate<T> {
    /**
     * 保存key
     *
     * @param key
     * @param value
     * @return
     */
     boolean setKey(String key, T value);
    /**
     * 获取缓存
     *
     * @param key
     * @return
     */
    T getKey(String key);
    /**
     * 删除
     *
     * @param key
     * @return
     */
    boolean delKey(String key);
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxException.java
New file
@@ -0,0 +1,55 @@
package com.ruoyi.system.utils.wx.tools;
/**
 * @author lihen
 */
public class WxException extends RuntimeException {
    private final static int OK = 0;
    private final static int ValidateSignatureError = -40001;
    private final static int ParseXmlError = -40002;
    public final static int ComputeSignatureError = -40003;
    private final static int IllegalAesKey = -40004;
    private final static int ValidateAppidError = -40005;
    private final static int EncryptAESError = -40006;
    private final static int DecryptAESError = -40007;
    private final static int IllegalBuffer = -40008;
    private int code;
    private static String getMessage(int code) {
        switch (code) {
            case ValidateSignatureError:
                return "签名验证错误";
            case ParseXmlError:
                return "xml解析失败";
            case ComputeSignatureError:
                return "sha加密生成签名失败";
            case IllegalAesKey:
                return "SymmetricKey非法";
            case ValidateAppidError:
                return "appid校验失败";
            case EncryptAESError:
                return "aes加密失败";
            case DecryptAESError:
                return "aes解密失败";
            case IllegalBuffer:
                return "解密后得到的buffer非法";
            default:
                return null;
        }
    }
    public int getCode() {
        return code;
    }
    WxException(int code) {
        super(getMessage(code));
        this.code = code;
    }
    public WxException(String message) {
        super(message);
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxJsonUtils.java
New file
@@ -0,0 +1,109 @@
package com.ruoyi.system.utils.wx.tools;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import lombok.extern.slf4j.Slf4j;
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
 * Json转换工具类
 * 参考:https://blog.csdn.net/weixin_38413579/article/details/82562634
 * @author madman
 */
@Slf4j
public final class WxJsonUtils {
    public static final String dateFormat = "yyyy-MM-dd";
    public static final String dateTimeFormat = "yyyy-MM-dd HH:mm:ss";
    private static final ObjectMapper OM = new ObjectMapper();
    private static final JavaTimeModule timeModule = new JavaTimeModule();
    /**
     * 转换LocalDateTime
     */
    static class LocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {
        @Override
        public void serialize(LocalDateTime localDateTime, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeString(localDateTime.format(DateTimeFormatter.ofPattern(dateTimeFormat)));
        }
    }
    /**
     * 转换LocalDate
     */
    static class LocalDateSerializer extends JsonSerializer<LocalDate> {
        @Override
        public void serialize(LocalDate localDate, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException {
            jsonGenerator.writeString(localDate.format(DateTimeFormatter.ofPattern(dateFormat)));
        }
    }
    /**
     * 设置 ObjectMapper
     *
     * @return
     */
    private static ObjectMapper getObjectMapper() {
        // 序列化
        timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer());
        timeModule.addSerializer(LocalDate.class, new LocalDateSerializer());
        // 反序列化
        timeModule.addDeserializer(LocalDateTime.class,
                new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(dateTimeFormat)));
        timeModule.addDeserializer(LocalDate.class,
                new LocalDateDeserializer(DateTimeFormatter.ofPattern(dateFormat)));
        // 允许对象忽略json中不存在的属性
        OM.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        OM.registerModule(timeModule);
        return OM;
    }
    /**
     * 将对象序列化
     */
    public static <T> String toJsonString(T obj) {
        try {
            ObjectMapper om = getObjectMapper();
            return om.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            log.error("转json字符串失败:{}", obj);
            return null;
        }
    }
    /**
     * 反序列化对象字符串
     */
    public static <T> T parseObject(String json, Class<T> clazz) {
        try {
            ObjectMapper om = getObjectMapper();
            return om.readValue(json, clazz);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("反序列化对象字符串失败");
        }
    }
    /**
     * 反序列化字符串成为对象
     */
    public static <T> T parseObject(String json, TypeReference<T> valueTypeRef) {
        try {
            ObjectMapper om = getObjectMapper();
            return om.readValue(json, valueTypeRef);
        } catch (JsonProcessingException e) {
            throw new RuntimeException("反序列化字符串成为对象失败");
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/utils/wx/tools/WxUtils.java
New file
@@ -0,0 +1,175 @@
package com.ruoyi.system.utils.wx.tools;
import com.ruoyi.system.utils.wx.pojo.AppletUserDecodeData;
import com.ruoyi.common.utils.sign.Base64;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.CharEncoding;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.AlgorithmParameters;
import java.security.Security;
import java.util.Arrays;
/**
 * @Description 获取用户信息工具类
 * @Author xiaochen
 * @Date 2021/8/12 15:45
 */
@Slf4j
public class WxUtils {
    /**
     * 微信小程序API 用户数据的解密
     *
     * @param encryptedData
     * @param sessionKey
     * @param iv
     * @return
     */
    public static AppletUserDecodeData encryptedData(String encryptedData, String sessionKey, String iv) {
        // 被加密的数据
        byte[] dataByte = Base64.decode(encryptedData);
        // 加密秘钥
        byte[] keyByte = Base64.decode(sessionKey);
        // 偏移量
        byte[] ivByte = Base64.decode(iv);
        try {
            // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
            int base = 16;
            if (keyByte.length % base != 0) {
                int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
                byte[] temp = new byte[groups * base];
                Arrays.fill(temp, (byte) 0);
                System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
                keyByte = temp;
            }
            // 初始化
            Security.addProvider(new BouncyCastleProvider());
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
            SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
            AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES");
            parameters.init(new IvParameterSpec(ivByte));
            cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
            byte[] resultByte = cipher.doFinal(dataByte);
            if (null != resultByte && resultByte.length > 0) {
                String result = new String(resultByte, CharEncoding.UTF_8);
                log.info("解密原串:{}", result);
                return WxJsonUtils.parseObject(result, AppletUserDecodeData.class);
            }
            throw new RuntimeException("解密的数据为空");
        } catch (Exception e) {
            log.error("解密失败. error = {}", e.getMessage(), e);
            throw new RuntimeException(e.getMessage());
        }
    }
    /**
     * 微信小程序API 用户数据的签名验证
     * signature = sha1( rawData + session_key )
     *
     * @param rawData    不包括敏感信息的原始数据字符串,用于计算签名。
     * @param sessionKey
     */
    public static void verifySignature(String rawData, String sessionKey, String signature) {
        String serverSignature = SHA1.getSHA1(rawData + sessionKey);
        log.info(rawData + ">>>>>>:" + sessionKey + " === " + serverSignature + "  ======" + signature);
        if (!signature.equals(serverSignature)) {
            throw new RuntimeException("数据验签不通过");
        }
    }
    /**
     * 根据流接收请求数据
     *
     * @param request
     * @return
     */
    public static String streamBodyByReceive(HttpServletRequest request) throws IOException {
        log.info("微信异步回调地址:{}", request.getRequestURL());
        StringBuffer buffer = new StringBuffer();
        InputStream inputStream = request.getInputStream();
        InputStreamReader reader = new InputStreamReader(inputStream);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String body = null;
        while ((body = bufferedReader.readLine()) != null) {
            buffer.append(body);
        }
        String data = buffer.toString();
        reader.close();
        inputStream.close();
        log.info("微信异步回调数据:{}", data);
        return data;
    }
    /**
     * 日志
     *
     * @return
     */
    public static Logger getLogger() {
        Logger logger = LoggerFactory.getLogger("wxpay java sdk");
        return logger;
    }
    /**
     * debug
     *
     * @param msg
     * @param args
     */
    public static void debug(String msg, Object... args) {
        Logger log = getLogger();
        if (log.isDebugEnabled()) {
            log.debug(msg, args);
        }
    }
    /**
     * info
     *
     * @param msg
     * @param args
     */
    public static void info(String msg, Object... args) {
        Logger log = getLogger();
        if (log.isInfoEnabled()) {
            log.info(msg, args);
        }
    }
    /**
     * warn
     *
     * @param msg
     * @param args
     */
    public static void warn(String msg, Object... args) {
        Logger log = getLogger();
        if (log.isWarnEnabled()) {
            log.warn(msg, args);
        }
    }
    /**
     * error
     *
     * @param msg
     * @param args
     */
    public static void error(String msg, Object... args) {
        Logger log = getLogger();
        if (log.isErrorEnabled()) {
            log.error(msg, args);
        }
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/vo/FaultConcatInfoVO.java
New file
@@ -0,0 +1,17 @@
package com.ruoyi.system.vo;
import com.ruoyi.system.model.TContract;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.io.Serializable;
@Data
@ApiModel(value = "报修房源信息VO")
public class FaultConcatInfoVO extends TContract {
    @ApiModelProperty(value = "房屋名称")
    private String houseName;
}
ruoyi-system/src/main/java/com/ruoyi/system/vo/SysUserVO.java
@@ -5,6 +5,8 @@
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(value = "账户列表VO")
public class SysUserVO extends SysUser {
@@ -14,7 +16,7 @@
    @ApiModelProperty(value = "单位名称")
    private String companyName;
    @ApiModelProperty(value = "部门")
    private String deptName;
    private List<String> deptList;
    @ApiModelProperty(value = "角色")
    private String roleName;
ruoyi-system/src/main/java/com/ruoyi/system/vo/TBillVO.java
@@ -1,6 +1,7 @@
package com.ruoyi.system.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.ruoyi.system.model.TBill;
import com.ruoyi.system.model.TBillDetail;
import com.ruoyi.system.model.THouse;
@@ -9,6 +10,7 @@
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Data
@@ -26,4 +28,18 @@
    @ApiModelProperty(value = "租金支付方式 月付 季付 年付")
    private String payType;
    @ApiModelProperty(value = "押金")
    private BigDecimal deposit;
    @ApiModelProperty(value = "甲方联系人")
    private String partyOnePerson;
    @ApiModelProperty(value = "甲方联系方式")
    private String partyOnePhone;
    @ApiModelProperty(value = "合同开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime concatStartTime;
    @ApiModelProperty(value = "合同结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private LocalDateTime concatEndTime;
}
ruoyi-system/src/main/java/com/ruoyi/system/vo/TCheckAcceptRecordVO.java
New file
@@ -0,0 +1,20 @@
package com.ruoyi.system.vo;
import com.ruoyi.system.model.TCheckAcceptRecord;
import com.ruoyi.system.model.TContract;
import com.ruoyi.system.model.THouse;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "验收记录VO")
public class TCheckAcceptRecordVO extends TCheckAcceptRecord {
    @ApiModelProperty(value = "合同信息")
    private TContract contract;
    @ApiModelProperty(value = "房屋信息")
    private THouse house;
}
ruoyi-system/src/main/java/com/ruoyi/system/vo/TFaultAreaDicVO.java
New file
@@ -0,0 +1,18 @@
package com.ruoyi.system.vo;
import com.ruoyi.system.model.TFaultAreaDic;
import com.ruoyi.system.model.TFaultDescribeDic;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(value = "故障区域vo")
public class TFaultAreaDicVO extends TFaultAreaDic {
    @ApiModelProperty(value = "故障描述")
    private List<TFaultDescribeDic> faultDescribeDicList;
}
ruoyi-system/src/main/java/com/ruoyi/system/vo/TFaultRepairMessageVO.java
New file
@@ -0,0 +1,22 @@
package com.ruoyi.system.vo;
import com.ruoyi.system.model.TFaultRepairMessage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel(value = "报修VO")
public class TFaultRepairMessageVO extends TFaultRepairMessage {
    @ApiModelProperty(value = "物品名称")
    private String itemName;
    @ApiModelProperty(value = "物品分类名称")
    private String itemTypeName;
    @ApiModelProperty(value = "用户名称")
    private String residentName;
}
ruoyi-system/src/main/java/com/ruoyi/system/vo/TItemTypeVO.java
New file
@@ -0,0 +1,18 @@
package com.ruoyi.system.vo;
import com.ruoyi.system.model.TItem;
import com.ruoyi.system.model.TItemType;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
@Data
@ApiModel(value = "物品类型VO")
public class TItemTypeVO extends TItemType {
    @ApiModelProperty(value = "维修物品集合")
    private List<TItem> itemList;
}
ruoyi-system/src/main/resources/mapper/system/SysOperLogMapper.xml
@@ -92,21 +92,6 @@
               sol.oper_time AS operTime, sol.cost_time AS costTime,sol.companyName AS companyName,sol.roleName AS roleName,sol.phonenumber AS phonenumber,
               sol.userId AS userId,sol.nickName AS nickName
        from sys_oper_log sol
        <where>
            <if test="query.companyName != null and query.companyName != ''">
                AND sol.companyName LIKE concat('%',#{query.companyName},'%')
            </if>
            <if test="query.roleName != null and query.roleName != ''">
                AND sol.roleName LIKE concat('%',#{query.roleName},'%')
            </if>
            <if test="query.nickName != null and query.nickName != ''">
                AND sol.nickName LIKE concat('%',#{query.nickName},'%')
            </if>
            <if test="query.startTime != null and query.startTime != '' and query.endTime != null and query.endTime != ''">
                AND DATE_FORMAT(sol.oper_time, '%Y-%m-%d %H:%i:%s') &gt;= #{query.startTime}
                AND DATE_FORMAT(sol.oper_time, '%Y-%m-%d %H:%i:%s') &lt;= #{query.endTime}
            </if>
        </where>
        ORDER BY sol.oper_time DESC
    </select>
ruoyi-system/src/main/resources/mapper/system/SysUserMapper.xml
@@ -228,19 +228,24 @@
        from sys_user u
        left join sys_user_role ur on u.user_id = ur.user_id
        left join sys_role r on r.role_id = ur.role_id
        left join t_dept_to_user tdtu on u.user_id = tdtu.user_id
        WHERE u.del_flag = 0
        <if test="query.nickName != null and query.nickName != ''">
            AND u.nick_name LIKE concat('%',#{query.nickName},'%')
        <if test="query.nickNameAndPhone != null and query.nickNameAndPhone != ''">
            AND (u.nick_name LIKE concat('%',#{query.nickNameAndPhone},'%')
                OR u.phonenumber LIKE concat('%',#{query.nickNameAndPhone},'%'))
        </if>
        <if test="query.roleId != null">
            AND r.role_id = #{query.roleId}
        </if>
        <if test="query.phonenumber != null and query.phonenumber != ''">
            AND u.phonenumber LIKE concat('%',#{query.phonenumber},'%')
        </if>
        <if test="query.status != null and query.status != ''">
            AND u.status = #{query.status}
        </if>
        <if test="query.deptIds != null and query.deptIds.size()>0">
            AND u.user_id IN (select DISTINCT user_id from t_dept_to_user where dept_id IN
            <foreach collection="query.deptIds" close=")" open="(" item="deptId" separator=",">
                #{deptId}
            </foreach>)
        </if>
        ORDER BY u.create_time DESC
    </select>
    <select id="selectIdByPhone" resultType="java.lang.Long">
ruoyi-system/src/main/resources/mapper/system/SysUserRoleMapper.xml
@@ -33,6 +33,12 @@
    <insert id="insertUserRole">
        insert into sys_user_role(user_id, role_id) values (#{userRole.userId},#{userRole.roleId})
    </insert>
    <insert id="insertBatchUserDept">
        insert into sys_user_role(user_id, dept_id) values
        <foreach item="item" index="index" collection="deptToUserList" separator=",">
            (#{item.userId},#{item.deptId})
        </foreach>
    </insert>
    <delete id="deleteUserRoleInfo" parameterType="SysUserRole">
        delete from sys_user_role where user_id=#{userId} and role_id=#{roleId}
ruoyi-system/src/main/resources/mapper/system/TCheckAcceptRecordMapper.xml
@@ -28,7 +28,53 @@
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, contract_id, house_id, check_time, lease_reason, check_person, accompany_person, overall_situation, furniture_situation, device_situation, clean_situation, other_problem, pictures, check_result, check_money, create_time, update_time, create_by, update_by, disabled
        id, contract_id, house_id, check_time, lease_reason, check_person, accompany_person, overall_situation, furniture_situation, device_situation, clean_situation,
            other_problem, pictures, check_result, check_money, create_time, update_time, create_by, update_by, disabled
    </sql>
    <select id="pageList" resultType="com.ruoyi.system.vo.TCheckAcceptRecordVO">
        select
        t.id,
        t.contract_id,
        t.house_id,
        t.check_time,
        t.lease_reason,
        t.check_person,
        t.accompany_person,
        t.overall_situation,
        t.furniture_situation,
        t.device_situation,
        t.clean_situation,
        t.other_problem,
        t.pictures,
        t.check_result,
        t.check_money,
        t.create_time,
        t.update_time,
        t.create_by,
        t.update_by,
        t.disabled,
        c.contract_number,
        h.house_name
        from t_check_accept_record t
        left join t_contract c on t.contract_id = c.id
        left join t_house h on t.house_id = h.id
        <where>
            <if test="query.contractNumber != null and query.contractNumber != ''">
                AND c.contract_number LIKE concat('%', #{query.contractNumber}, '%')
            </if>
            <if test="query.checkResult != null">
                AND t.check_result = #{query.checkResult}
            </if>
            <if test="query.houseName != null and query.houseName != ''">
                AND h.house_name LIKE concat('%', #{query.houseName}, '%')
            </if>
            <if test="query.startTime != null and query.startTime != '' and query.endTime != null and query.endTime != ''">
                AND t.check_time &gt;= #{query.startTime}
                AND t.check_time &lt;= #{query.endTime}
            </if>
            AND t.disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
        </where>
        ORDER BY t.create_time DESC
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/TContractMapper.xml
@@ -80,7 +80,7 @@
        select t1.*,t2.contract_number as contractNumber,t3.resident_name as residentName,t3.phone as phone
        from t_bill t1
        left join t_contract t2 on t1.contract_id = t2.id
        left join t_resident t3 on t2.tenant_id = t3.id
        left join t_tenant t3 on t2.tenant_id = t3.id
        where t2.id = #{query.id}
        and (t1.pay_fees_status = 1 or t1.pay_fees_status = 4)
        AND t1.disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
ruoyi-system/src/main/resources/mapper/system/TDeptToUserMapper.xml
@@ -13,5 +13,8 @@
    <sql id="Base_Column_List">
        id, dept_id, user_id
    </sql>
    <delete id="deleteUserDeptByUserId">
        delete from t_dept_to_user where user_id=#{userId}
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/TFaultAreaDicMapper.xml
@@ -24,5 +24,11 @@
        WHERE disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
        ORDER BY sort_by,create_time DESC
    </select>
    <select id="getAreaDicList" resultType="com.ruoyi.system.vo.TFaultAreaDicVO">
        SELECT <include refid="Base_Column_List"></include>
        FROM t_fault_area_dic
        WHERE disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
        ORDER BY sort_by,create_time DESC
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/TFaultRepairMessageMapper.xml
@@ -5,8 +5,10 @@
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="com.ruoyi.system.model.TFaultRepairMessage">
        <id column="id" property="id" />
        <result column="app_user_id" property="appUserId" />
        <result column="tenant_id" property="tenantId" />
        <result column="item_id" property="itemId" />
        <result column="item_type_id" property="itemTypeId" />
        <result column="contract_id" property="contractId" />
        <result column="fault_area_name" property="faultAreaName" />
        <result column="describe_name" property="describeName" />
        <result column="describe_detail" property="describeDetail" />
@@ -18,9 +20,10 @@
        <result column="leave_message" property="leaveMessage" />
        <result column="handle_person" property="handlePerson" />
        <result column="handle_time" property="handleTime" />
        <result column="reault_describe" property="reaultDescribe" />
        <result column="result_describe" property="resultDescribe" />
        <result column="repair_picture" property="repairPicture" />
        <result column="attachment" property="attachment" />
        <result column="attachment_name" property="attachmentName" />
        <result column="status" property="status" />
        <result column="create_time" property="createTime" />
        <result column="update_time" property="updateTime" />
@@ -31,7 +34,101 @@
    <!-- 通用查询结果列 -->
    <sql id="Base_Column_List">
        id, app_user_id, item_id, fault_area_name, describe_name, describe_detail, fault_pictures, service_address, repair_type, visit_time, contact_number, leave_message, handle_person, handle_time, reault_describe, repair_picture, attachment, status, create_time, update_time, create_by, update_by, disabled
        id, tenant_id, item_id,item_type_id,contract_id, fault_area_name, describe_name, describe_detail, fault_pictures, service_address, repair_type,
            visit_time, contact_number, leave_message, handle_person, handle_time, result_describe, repair_picture, attachment,attachment_name, status,
            create_time,update_time, create_by, update_by, disabled
    </sql>
    <select id="getDetailById" resultType="com.ruoyi.system.vo.TFaultRepairMessageVO">
        SELECT
        t.id,
        t.tenant_id,
        t.item_id,
        t.item_type_id,
        t.contract_id,
        t.fault_area_name,
        t.describe_name,
        t.describe_detail,
        t.fault_pictures,
        t.service_address,
        t.repair_type,
        t.visit_time,
        t.contact_number,
        t.leave_message,
        t.handle_person,
        t.handle_time,
        t.result_describe,
        t.repair_picture,
        t.attachment,
        t.attachment_name,
        t.status,
        t.create_time,
        t.update_time,
        t.create_by,
        t.update_by,
        t.disabled,
        i.item_name AS itemName,
        it.type_name AS itemTypeName,
        tnt.resident_name AS residentName
        from t_fault_repair_message t
        LEFT JOIN t_item i ON t.item_id = i.id
        LEFT JOIN t_item_type it ON t.item_type_id = it.id
        LEFT JOIN t_tenant tnt ON t.tenant_id = tnt.id
        WHERE t.id = #{id}
    </select>
    <select id="pageList" resultType="com.ruoyi.system.vo.TFaultRepairMessageVO">
        SELECT
            t.id,
            t.tenant_id,
            t.item_id,
            t.item_type_id,
            t.contract_id,
            t.fault_area_name,
            t.describe_name,
            t.describe_detail,
            t.fault_pictures,
            t.service_address,
            t.repair_type,
            t.visit_time,
            t.contact_number,
            t.leave_message,
            t.handle_person,
            t.handle_time,
            t.result_describe,
            t.repair_picture,
            t.attachment,
            t.attachment_name,
            t.status,
            t.create_time,
            t.update_time,
            t.create_by,
            t.update_by,
            t.disabled,
            i.item_name AS itemName,
            it.type_name AS itemTypeName,
            tnt.resident_name AS residentName
        from t_fault_repair_message t
                 LEFT JOIN t_item i ON t.item_id = i.id
                 LEFT JOIN t_item_type it ON t.item_type_id = it.id
                 LEFT JOIN t_tenant tnt ON t.tenant_id = tnt.id
        <where>
            <if test="query.tenantId != null and query.tenantId != ''">
                AND t.tenant_id = #{query.tenantId}
            </if>
            <if test="query.contactNumber != null and query.contactNumber != ''">
                AND t.contact_number LIKE CONCAT('%', #{query.contactNumber}, '%')
            </if>
            <if test="query.status != null">
                AND t.status = #{query.status}
            </if>
            <if test="query.repairType != null">
                AND t.repair_type = #{query.repairType}
            </if>
            <if test="query.handlePerson != null and query.handlePerson != ''">
                AND t.handle_person LIKE CONCAT('%', #{query.handlePerson}, '%')
            </if>
            AND t.disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
        </where>
        ORDER BY t.create_time DESC
    </select>
</mapper>
ruoyi-system/src/main/resources/mapper/system/TItemMapper.xml
@@ -22,7 +22,7 @@
    </sql>
    <select id="pageList" resultType="com.ruoyi.system.vo.TItemVO">
        select
        t.id, t.type_id, t.item_name, t.sort_by, t.pictures, t.create_time, t.update_time, t.create_by, t.update_by, t.disabled
        t.id, t.type_id, t.item_name, t.sort_by, t.pictures, t.create_time, t.update_time, t.create_by, t.update_by, t.disabled,
        tt.type_name
        from t_item t
        left join t_item_type tt on t.type_id = tt.id
ruoyi-system/src/main/resources/mapper/system/TItemTypeMapper.xml
@@ -25,5 +25,12 @@
        where disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
        ORDER BY sort_by,create_time DESC
    </select>
    <select id="getItemList" resultType="com.ruoyi.system.vo.TItemTypeVO">
        select
        <include refid="Base_Column_List"/>
        from t_item_type
        where disabled = ${@com.ruoyi.common.enums.DisabledEnum@NO.getCode()}
        ORDER BY sort_by,create_time DESC
    </select>
</mapper>