feat: 账单发送提醒

This commit is contained in:
Bunny 2024-12-19 13:51:15 +08:00
parent 17ec9cbe3b
commit 2c6a6e9b4b
28 changed files with 544 additions and 123 deletions

View File

@ -36,4 +36,9 @@ public class EmailTemplateAddDto {
@Schema(name = "type", title = "邮件类型")
private String type;
@Schema(name = "isDefault", title = "是否默认")
@NotNull(message = "是否默认不能为空")
private Boolean isDefault;
}

View File

@ -24,5 +24,9 @@ public class EmailTemplateDto {
@Schema(name = "type", title = "邮件类型")
private String type;
@Schema(name = "isDefault", title = "是否默认")
private Boolean isDefault;
}

View File

@ -40,4 +40,9 @@ public class EmailTemplateUpdateDto {
@Schema(name = "type", title = "邮件类型")
private String type;
@Schema(name = "isDefault", title = "是否默认")
@NotNull(message = "是否默认不能为空")
private Boolean isDefault;
}

View File

@ -46,6 +46,12 @@ public class AdminUser extends BaseEntity {
@Schema(name = "summary", title = "个人描述")
private String summary;
@Schema(name = "weekBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean weekBillReport;
@Schema(name = "mouthBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean mouthBillReport;
@Schema(name = "ipAddress", title = "登录Ip")
private String ipAddress;

View File

@ -40,4 +40,4 @@ public class EmailTemplate extends BaseEntity {
@Schema(name = "isDefault", title = "是否默认")
private Boolean isDefault;
}
}

View File

@ -7,6 +7,7 @@ import lombok.Getter;
@Schema(description = "Email Template Types")
public enum EmailTemplateEnums {
VERIFICATION_CODE("verification_code", "邮箱验证码发送"),
BILL_WEEK_NOTIFICATION("bill_week_notification", "周账单提醒"),
NOTIFICATION("notification", "通知型邮件"),
WARNING("warning", "警告型邮件"),
;

View File

@ -18,6 +18,7 @@ public enum ResultCodeEnum {
LOGOUT_SUCCESS(200, "退出成功"),
EMAIL_CODE_REFRESH(200, "邮箱验证码已刷新"),
EMAIL_CODE_SEND_SUCCESS(200, "邮箱验证码已发送"),
EMAIL_SEND_SUCCESS(200, "邮件已发送"),
// 验证错误 201
USERNAME_OR_PASSWORD_NOT_EMPTY(201, "用户名或密码不能为空"),

View File

@ -35,6 +35,9 @@ public class EmailTemplateVo extends BaseUserVo {
@Schema(name = "type", title = "邮件类型")
private String type;
@Schema(name = "summary", title = "邮件类型详情")
private String summary;
@Schema(name = "isDefault", title = "是否默认")
private Boolean isDefault;

View File

@ -36,6 +36,12 @@ public class AdminUserVo extends BaseUserVo {
@Schema(name = "summary", title = "个人描述")
private String summary;
@Schema(name = "weekBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean weekBillReport;
@Schema(name = "mouthBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean mouthBillReport;
@Schema(name = "ipAddress", title = "登录Ip")
private String ipAddress;

View File

@ -48,6 +48,12 @@ public class LoginVo extends BaseVo {
@Schema(name = "ipRegion", title = "登录Ip归属地")
private String ipRegion;
@Schema(name = "weekBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean weekBillReport;
@Schema(name = "mouthBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean mouthBillReport;
@Schema(name = "status", title = "1:禁用 0:正常")
private Boolean status;

View File

@ -27,6 +27,12 @@ public class UserVo extends BaseVo {
@Schema(name = "avatar", title = "头像")
private String avatar;
@Schema(name = "weekBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean weekBillReport;
@Schema(name = "mouthBillReport", title = "周报提醒", description = "1:启用 0:关闭")
private Boolean mouthBillReport;
@Schema(name = "sex", title = "0:女 1:男")
private Byte sex;

View File

@ -1,20 +0,0 @@
package cn.bunny.dao.vo.system.user;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Schema(description = "验证码响应结果实体类")
public class ValidateCodeVo {
@Schema(description = "验证码key")
private String codeKey;
@Schema(description = "验证码value")
private String codeValue;
}

View File

@ -77,6 +77,13 @@ public class BillController {
return Mono.just(Result.success(vo));
}
@Operation(summary = "发送账单提醒", description = "发送账单提醒")
@GetMapping("noManage/sendBillReport")
public Result<String> sendBillReport(String type) {
billService.sendBillReport(type);
return Result.success(ResultCodeEnum.EMAIL_SEND_SUCCESS);
}
@Operation(summary = "使用Excel导入用户账单", description = "使用Excel导入用户账单")
@PostMapping("noManage/importBill")
public Mono<Result<Result<String>>> importBill(MultipartFile file) {

View File

@ -82,6 +82,13 @@ public class UserController {
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
}
@Operation(summary = "更新用户账单是否启用", description = "更新用户账单是否启用")
@PutMapping("noManage/updateUserBillReportByLocalUser")
public Result<String> updateUserBillReportByLocalUser(String type, Boolean status) {
userService.updateUserBillReportByLocalUser(type, status);
return Result.success(ResultCodeEnum.UPDATE_SUCCESS);
}
@Operation(summary = "管理员修改用户密码", description = "管理员修改管理员用户密码")
@PutMapping("updateUserPasswordByAdmin")
public Result<String> updateUserPasswordByAdmin(@Valid @RequestBody AdminUserUpdateWithPasswordDto dto) {

View File

@ -3,8 +3,12 @@ package cn.bunny.services.factory;
import cn.bunny.common.service.exception.AuthCustomerException;
import cn.bunny.dao.dto.financial.bill.IncomeExpenseQueryDto;
import cn.bunny.dao.dto.financial.bill.excel.BillExportDto;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.dao.entity.system.EmailTemplate;
import cn.bunny.dao.excel.BillExportExcelByUser;
import cn.bunny.dao.pojo.constant.LocalDateTimeConstant;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import cn.bunny.dao.vo.financial.admin.BillVo;
import cn.bunny.dao.vo.financial.user.expendAndIncome.ExpendWithIncome;
import cn.bunny.services.mapper.financial.BillMapper;
import com.alibaba.excel.EasyExcel;
@ -16,14 +20,18 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
@Component
public class BillFactory {
@ -31,6 +39,9 @@ public class BillFactory {
@Autowired
private BillMapper billMapper;
@Autowired
private EmailFactory emailFactory;
public void exportBill(BillExportDto dto, HttpServletResponse response) {
LocalDate startDate = dto.getStartDate();
LocalDate endDate = dto.getEndDate();
@ -94,4 +105,67 @@ public class BillFactory {
throw new RuntimeException(e);
}
}
/**
* 整理当前日期和参数并发送邮件
*
* @param startOfDay 开始日期
* @param endOfDay 结束日期
* @param billVoList 账单列表
* @param adminUser 用户信息
* @param emailTemplate 邮件模板
*/
public void executeSendEmail(LocalDate startOfDay, LocalDate endOfDay, List<BillVo> billVoList, AdminUser adminUser, EmailTemplate emailTemplate) {
// 发送账单日期
String sendDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS));
String startDate = startOfDay.format(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD));
String endDate = endOfDay.format(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD));
// 计算总收入
double income = billVoList.stream()
.filter(billVo -> billVo.getType().equals(Byte.parseByte("1")))
.collect(Collectors.summarizingDouble(billVo -> billVo.getAmount().doubleValue()))
.getSum();
// 计算总支出
double expend = billVoList.stream()
.filter(billVo -> billVo.getType().equals(Byte.parseByte("-1")))
.collect(Collectors.summarizingDouble(billVo -> billVo.getAmount().doubleValue()))
.getSum();
// 计算盈利
double profit = income - expend;
// 计算盈利
double percentValue = (income + expend) == 0 ? 0 : expend / (income + expend);
BigDecimal percent = new BigDecimal(percentValue * 100);
// 表格字符串
StringBuilder billTable = new StringBuilder();
billVoList.forEach(billVo -> {
// 计算是支出还是收入
BigDecimal amount = billVo.getAmount();
int type = billVo.getType();
amount = amount.multiply(new BigDecimal(type));
String tr = " <td style=\"font-weight:bolder;\">" + billVo.getCategoryName() + "</td>\n" +
" <td style=\"color:#DC684A;font-weight:bolder;\">" + amount + " ¥</td>\n" +
" <td style=\"font-weight:bolder;\">" + billVo.getDescription() + "</td>\n" +
"</tr>";
billTable.append(tr);
});
// 替换整理参数
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("##billName##", adminUser.getNickname());
hashMap.put("##billDate##", startDate + "~" + endDate);
hashMap.put("##income##", income);
hashMap.put("##expend##", expend);
hashMap.put("##profit##", new BigDecimal(profit).setScale(2, RoundingMode.HALF_UP));
hashMap.put("##percent##", percent.setScale(2, RoundingMode.HALF_UP));
hashMap.put("##billTable##", billTable);
hashMap.put("##sendDate##", sendDate);
emailFactory.sendEmailTemplate(adminUser.getEmail(), emailTemplate, hashMap);
}
}

View File

@ -32,7 +32,7 @@ public interface BillMapper extends BaseMapper<Bill> {
void deleteBatchIdsWithPhysics(List<Long> ids);
/**
* * 分页查询账单信息内容
* 分页查询账单信息内容
*
* @param pageParams 账单信息分页参数
* @param dto 账单信息查询表单
@ -40,6 +40,15 @@ public interface BillMapper extends BaseMapper<Bill> {
*/
IPage<BillVo> selectListByPage(@Param("page") Page<Bill> pageParams, @Param("dto") BillDto dto);
/**
* 查询账单信息内容
*
* @param dto 账单信息查询表单
* @return 账单信息结果
*/
List<BillVo> selectListByBillNotification(@Param("dto") BillDto dto);
/**
* 账单收入和支出
*

View File

@ -1,75 +0,0 @@
package cn.bunny.services.quartz;
import cn.bunny.services.aop.annotation.QuartzSchedulers;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@Slf4j
@QuartzSchedulers(type = "backup", description = "数据库备份(仅限本地docker中MySQL)")
@Component
public class DatabaseBackupJob implements Job {
@Value("${bunny.backPath}")
private String backPath;
@SneakyThrows
@Override
public void execute(JobExecutionContext context) {
// 读取资源目录下脚本文件并写入到主机中
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream("static/backup.sh")) {
if (inputStream == null) return;
byte[] bytes = inputStream.readAllBytes();
Files.write(Path.of(backPath + "/backup.sh"), bytes);
}
// 执行脚本
System.setProperty("TERM", "xterm");
ProcessBuilder processBuilder = new ProcessBuilder("sh", backPath + "/backup.sh");
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();
// 执行命令
ProcessHandle handle = process.toHandle();
// 执行任务的pid
long pid = handle.pid();
// 执行任务系统信息
JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
Map<String, String> environment = processBuilder.environment();
String info = handle.info().toString();
jobDataMap.put("pid", pid);
jobDataMap.put("systemInfo", info);
jobDataMap.put("environment", environment);
// 进程是否结束
if (process.waitFor(5, TimeUnit.MINUTES)) {
int waitedFor = process.waitFor();
jobDataMap.put("existCode", waitedFor);
} else process.destroyForcibly();
// 执行后读取内容
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
StringBuilder stringBuilder = new StringBuilder();
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
System.out.println(line);
}
jobDataMap.put("output", stringBuilder.toString());
}
}

View File

@ -1,15 +1,73 @@
package cn.bunny.services.quartz;
import cn.bunny.dao.dto.financial.bill.BillDto;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.dao.entity.system.EmailTemplate;
import cn.bunny.dao.pojo.enums.EmailTemplateEnums;
import cn.bunny.dao.vo.financial.admin.BillVo;
import cn.bunny.services.aop.annotation.QuartzSchedulers;
import cn.bunny.services.factory.EmailFactory;
import cn.bunny.services.factory.BillFactory;
import cn.bunny.services.mapper.email.EmailTemplateMapper;
import cn.bunny.services.mapper.financial.BillMapper;
import cn.bunny.services.mapper.system.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@QuartzSchedulers(type = "email-overhead-report", description = "定时邮件任务")
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
@QuartzSchedulers(type = "email-bill-week-report", description = "账单邮件任务-周")
@Component
public class MailingJob implements Job {
@Autowired
private EmailTemplateMapper emailTemplateMapper;
@Autowired
private BillMapper billMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private BillFactory billFactory;
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
EmailFactory emailFactory = new EmailFactory();
public void execute(JobExecutionContext jobExecutionContext) {
LocalDate today = LocalDate.now();
LocalDate startOfWeek = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate endOfWeek = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
// 查询验证码邮件模板
LambdaQueryWrapper<EmailTemplate> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(EmailTemplate::getIsDefault, true);
lambdaQueryWrapper.eq(EmailTemplate::getType, EmailTemplateEnums.BILL_WEEK_NOTIFICATION.getType());
EmailTemplate emailTemplate = emailTemplateMapper.selectOne(lambdaQueryWrapper);
// 查询需要发送邮件的用户
LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(AdminUser::getWeekBillReport, true)
.eq(AdminUser::getStatus, false)// 没有被禁用的
.isNotNull(AdminUser::getEmail);// 有邮箱的
List<AdminUser> userList = userMapper.selectList(queryWrapper);
userList.forEach(user -> {
Long userId = user.getId();
String email = user.getEmail();
// 查询当前用户账单数据
BillDto billDto = new BillDto();
billDto.setUserId(userId);
billDto.setStartDate(startOfWeek);
billDto.setEndDate(endOfWeek.plusDays(1));
List<BillVo> billVoList = billMapper.selectListByBillNotification(billDto);
billFactory.executeSendEmail(startOfWeek, endOfWeek, billVoList, user, emailTemplate);
});
}
}

View File

@ -11,6 +11,7 @@ import cn.bunny.dao.pojo.result.ResultCodeEnum;
import cn.bunny.dao.vo.system.email.EmailTemplateVo;
import cn.bunny.services.mapper.email.EmailTemplateMapper;
import cn.bunny.services.service.email.EmailTemplateService;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -18,11 +19,9 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import jakarta.validation.Valid;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
/**
* <p>
@ -33,6 +32,7 @@ import java.util.Map;
* @since 2024-10-10 21:24:08
*/
@Service
@Transactional
public class EmailTemplateServiceImpl extends ServiceImpl<EmailTemplateMapper, EmailTemplate> implements EmailTemplateService {
/**
@ -45,9 +45,22 @@ public class EmailTemplateServiceImpl extends ServiceImpl<EmailTemplateMapper, E
@Override
public PageResult<EmailTemplateVo> getEmailTemplateList(Page<EmailTemplate> pageParams, EmailTemplateDto dto) {
IPage<EmailTemplateVo> page = baseMapper.selectListByPage(pageParams, dto);
List<EmailTemplateVo> emailTemplateVos = page.getRecords().stream()
.map(emailTemplateVo -> {
EmailTemplateVo vo = new EmailTemplateVo();
BeanUtils.copyProperties(emailTemplateVo, vo);
// 查找枚举内容并设置详情
List<EmailTemplateEnums> emailTemplateEnums = Arrays.stream(EmailTemplateEnums.values())
.filter(enums -> enums.getType().equals(vo.getType()))
.toList();
vo.setSummary(emailTemplateEnums.get(0).getSummary());
return vo;
})
.toList();
return PageResult.<EmailTemplateVo>builder()
.list(page.getRecords())
.list(emailTemplateVos)
.pageNo(page.getCurrent())
.pageSize(page.getSize())
.total(page.getTotal())
@ -61,9 +74,28 @@ public class EmailTemplateServiceImpl extends ServiceImpl<EmailTemplateMapper, E
*/
@Override
public void addEmailTemplate(@Valid EmailTemplateAddDto dto) {
String type = dto.getType();
// 保存数据
EmailTemplate emailTemplate = new EmailTemplate();
BeanUtils.copyProperties(dto, emailTemplate);
// 判断当前类型是否已有默认如果是不设置为默认直接保存
if (!dto.getIsDefault()) {
save(emailTemplate);
return;
}
LambdaQueryWrapper<EmailTemplate> emailTemplateLambdaQueryWrapper = new LambdaQueryWrapper<>();
emailTemplateLambdaQueryWrapper.eq(EmailTemplate::getType, type);
emailTemplateLambdaQueryWrapper.eq(EmailTemplate::getIsDefault, Boolean.TRUE);
List<EmailTemplate> emailTemplateList = list(emailTemplateLambdaQueryWrapper);
// 更新列表
List<EmailTemplate> updateList = emailTemplateList.stream().map(template -> template.setIsDefault(false)).toList();
if (!updateList.isEmpty()) {
updateBatchById(updateList);
}
save(emailTemplate);
}
@ -74,6 +106,8 @@ public class EmailTemplateServiceImpl extends ServiceImpl<EmailTemplateMapper, E
*/
@Override
public void updateEmailTemplate(@Valid EmailTemplateUpdateDto dto) {
String type = dto.getType();
// 查询是否有这个模板
List<EmailTemplate> emailTemplateList = list(Wrappers.<EmailTemplate>lambdaQuery().eq(EmailTemplate::getId, dto.getId()));
if (emailTemplateList.isEmpty()) throw new AuthCustomerException(ResultCodeEnum.DATA_NOT_EXIST);
@ -81,7 +115,31 @@ public class EmailTemplateServiceImpl extends ServiceImpl<EmailTemplateMapper, E
// 更新内容
EmailTemplate emailTemplate = new EmailTemplate();
BeanUtils.copyProperties(dto, emailTemplate);
updateById(emailTemplate);
// 判断当前类型是否已有默认如果是不设置为默认直接保存
List<EmailTemplate> updateList = new ArrayList<>();
if (dto.getIsDefault()) {
LambdaQueryWrapper<EmailTemplate> emailTemplateLambdaQueryWrapper = new LambdaQueryWrapper<>();
emailTemplateLambdaQueryWrapper.eq(EmailTemplate::getType, type);
emailTemplateLambdaQueryWrapper.eq(EmailTemplate::getIsDefault, Boolean.TRUE);
// 更新数据列表
List<EmailTemplate> checkEmailTemplateList = list(emailTemplateLambdaQueryWrapper);
updateList = new ArrayList<>(checkEmailTemplateList.stream().map(template -> template.setIsDefault(false)).toList());
}
// 更新数据
updateList.add(emailTemplate);
updateBatchById(updateList);
// 默认邮件
List<EmailTemplate> emailTemplates = list(Wrappers.<EmailTemplate>lambdaQuery().eq(EmailTemplate::getType, type));
List<EmailTemplate> isEmailTemplateListEmpty = emailTemplates.stream().filter(template -> template.getIsDefault().equals(true)).toList();
if (isEmailTemplateListEmpty.isEmpty()) {
EmailTemplate template = emailTemplates.get(0);
template.setIsDefault(Boolean.TRUE);
updateById(template);
}
}
/**

View File

@ -115,4 +115,11 @@ public interface BillService extends IService<Bill> {
* @param file 文件
*/
void importBill(MultipartFile file);
/**
* 发送账单提醒
*
* @param type 账单类型
*/
void sendBillReport(String type);
}

View File

@ -11,6 +11,9 @@ import cn.bunny.dao.dto.financial.bill.excel.BillImportByUserDto;
import cn.bunny.dao.dto.financial.bill.user.BillAddByUserDto;
import cn.bunny.dao.dto.financial.bill.user.BillUpdateByUserDto;
import cn.bunny.dao.entity.financial.Bill;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.dao.entity.system.EmailTemplate;
import cn.bunny.dao.pojo.enums.EmailTemplateEnums;
import cn.bunny.dao.pojo.result.PageResult;
import cn.bunny.dao.pojo.result.ResultCodeEnum;
import cn.bunny.dao.vo.financial.admin.BillVo;
@ -21,12 +24,15 @@ import cn.bunny.dao.vo.financial.user.expendAndIncome.ExpendWithIncomeListVo;
import cn.bunny.services.excel.BillAddUserListener;
import cn.bunny.services.factory.BillFactory;
import cn.bunny.services.factory.HomeFactory;
import cn.bunny.services.mapper.email.EmailTemplateMapper;
import cn.bunny.services.mapper.financial.BillMapper;
import cn.bunny.services.mapper.financial.CategoryMapper;
import cn.bunny.services.mapper.message.MessageMapper;
import cn.bunny.services.mapper.message.MessageReceivedMapper;
import cn.bunny.services.mapper.system.UserMapper;
import cn.bunny.services.service.financial.BillService;
import com.alibaba.excel.EasyExcel;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
@ -35,10 +41,13 @@ import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;
import java.util.List;
import java.util.stream.Collectors;
@ -61,10 +70,16 @@ public class BillServiceImpl extends ServiceImpl<BillMapper, Bill> implements Bi
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private MessageMapper messageMapper;
@Autowired
private MessageReceivedMapper messageReceivedMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private EmailTemplateMapper emailTemplateMapper;
/**
* * 账单信息 服务实现类
@ -267,6 +282,54 @@ public class BillServiceImpl extends ServiceImpl<BillMapper, Bill> implements Bi
}
}
/**
* 发送账单提醒
*
* @param type 账单类型
*/
@Override
public void sendBillReport(String type) {
LocalDate today = LocalDate.now();
if (!StringUtils.hasText(type)) {
throw new AuthCustomerException(ResultCodeEnum.ILLEGAL_DATA_REQUEST);
}
// 这个日期的开始星期和结束星期
LocalDate startOfWeek = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate endOfWeek = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
// 这个日期的开始月份和结束月份
LocalDate startOfMouth = today.withDayOfMonth(1);
LocalDate endOfMouth = today.withDayOfMonth(today.lengthOfMonth());
// 查询当前用户
Long userId = BaseContext.getUserId();
AdminUser adminUser = userMapper.selectById(userId);
// 查询验证码邮件模板
LambdaQueryWrapper<EmailTemplate> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(EmailTemplate::getIsDefault, true);
lambdaQueryWrapper.eq(EmailTemplate::getType, EmailTemplateEnums.BILL_WEEK_NOTIFICATION.getType());
EmailTemplate emailTemplate = emailTemplateMapper.selectOne(lambdaQueryWrapper);
// 查询当前用户账单数据
BillDto billDto = new BillDto();
billDto.setUserId(userId);
if (type.equals("week")) {
billDto.setStartDate(startOfWeek);
billDto.setEndDate(endOfWeek.plusDays(1));
List<BillVo> billVoList = baseMapper.selectListByBillNotification(billDto);
billFactory.executeSendEmail(startOfWeek, endOfWeek, billVoList, adminUser, emailTemplate);
} else if (type.equals("mouth")) {
billDto.setStartDate(startOfMouth);
billDto.setEndDate(endOfMouth.plusDays(1));
List<BillVo> billVoList = baseMapper.selectListByBillNotification(billDto);
billFactory.executeSendEmail(startOfMouth, endOfMouth, billVoList, adminUser, emailTemplate);
}
}
/**
* 删除|批量删除账单信息
*

View File

@ -134,4 +134,12 @@ public interface UserService extends IService<AdminUser> {
* @param password 更新本地用户密码
*/
void updateUserPasswordByLocalUser(@Valid String password);
/**
* 更新用户账单是否启用
*
* @param type 类型
* @param status 状态
*/
void updateUserBillReportByLocalUser(String type, Boolean status);
}

View File

@ -327,6 +327,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
*/
@Override
public LoginVo getUserinfo() {
return BaseContext.getLoginVo();
}
@ -388,6 +389,34 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, AdminUser> implemen
redisTemplate.delete(RedisUserConstant.getAdminLoginInfoPrefix(adminUser.getUsername()));
}
/**
* 更新用户账单是否启用
*
* @param type 类型
* @param status 状态
*/
@Override
public void updateUserBillReportByLocalUser(String type, Boolean status) {
AdminUser adminUser = new AdminUser();
adminUser.setId(BaseContext.getUserId());
if (!StringUtils.hasText(type)) {
throw new AuthCustomerException(ResultCodeEnum.ILLEGAL_DATA_REQUEST);
}
// 判断类型是什么
if (type.equals("week")) {
adminUser.setWeekBillReport(status);
} else if (type.equals("mouth")) {
adminUser.setMouthBillReport(status);
}
updateById(adminUser);
adminUser = getById(adminUser.getId());
userFactory.buildUserVo(adminUser, RedisUserConstant.REDIS_EXPIRATION_TIME);
}
/**
* * 用户信息 服务实现类
*

View File

@ -46,6 +46,9 @@
<if test="dto.type != null and dto.type != ''">
and template.type like CONCAT('%',#{dto.type},'%')
</if>
<if test="dto.isDefault != null and dto.isDefault != ''">
and template.isDefault = #{dto.isDefault}
</if>
</where>
</select>

View File

@ -75,6 +75,41 @@
order by b.amount desc
</select>
<!-- 查询账单信息内容 -->
<select id="selectListByBillNotification" resultType="cn.bunny.dao.vo.financial.admin.BillVo">
select
base.*,
category.category_name,
category.id as category_id,
baseuser.username as username,
create_user.username as create_username,
update_user.username as update_username
from t_bill base
left join t_category category on category.id = base.category_id
left join sys_user baseuser on baseuser.id = base.user_id
left join sys_user create_user on create_user.id = base.create_user
left join sys_user update_user on update_user.id = base.update_user
<where>
base.is_deleted = 0
<if test="dto.userId != null and dto.userId != ''">
and base.user_id = #{dto.userId}
</if>
<if test="dto.type != null and dto.type != ''">
and base.type = #{dto.type}
</if>
<if test="dto.description != null and dto.description != ''">
and base.description like CONCAT('%',#{dto.description},'%')
</if>
<if test="dto.amount != null">
and base.amount like CONCAT('%',#{dto.amount},'%')
</if>
<if test="dto.startDate != null and dto.endDate != null">
and base.transaction_date between #{dto.startDate} and #{dto.endDate}
</if>
</where>
order by base.transaction_date
</select>
<!-- 物理删除账单信息 -->
<delete id="deleteBatchIdsWithPhysics">
delete

View File

@ -1,15 +0,0 @@
#!/bin/bash
# 设置备份文件存放目录
backup_dir="/home/backup/"
# 获取当前时间并格式化为 yyyy_MM_dd_HH_mm_ss_SSS 格式
timestamp=$(date +"%Y_%m_%d_%H_%M_%S_%3N")
# 设置数据库用户名、密码和数据库名
db_user="root"
db_pass="02120212"
db_name="auth_admin"
# 设置备份文件名
backup_file="${backup_dir}backup_${db_name}_${timestamp}.sql"
# 执行备份命令
docker exec -i slave_3304 bash -c "mysqldump -u ${db_user} -p${db_pass} ${db_name} > ${backup_file}"
# 输出备份文件路径
echo "Backup completed: ${backup_file}"

View File

@ -0,0 +1,15 @@
package cn.bunny.services;
import cn.bunny.dao.pojo.enums.EmailTemplateEnums;
import java.util.Arrays;
import java.util.List;
public class EnumsTest {
@org.junit.jupiter.api.Test
void emailEnums() {
List<EmailTemplateEnums> verificationCode = Arrays.stream(EmailTemplateEnums.values()).filter(emailTemplateEnums -> emailTemplateEnums.getType().equals("verification_code")).toList();
System.out.println(verificationCode.get(0).getSummary());
}
}

View File

@ -0,0 +1,115 @@
package cn.bunny.services.quartz;
import cn.bunny.dao.dto.financial.bill.BillDto;
import cn.bunny.dao.entity.system.AdminUser;
import cn.bunny.dao.entity.system.EmailTemplate;
import cn.bunny.dao.pojo.constant.LocalDateTimeConstant;
import cn.bunny.dao.pojo.enums.EmailTemplateEnums;
import cn.bunny.dao.vo.financial.admin.BillVo;
import cn.bunny.services.factory.EmailFactory;
import cn.bunny.services.mapper.email.EmailTemplateMapper;
import cn.bunny.services.mapper.financial.BillMapper;
import cn.bunny.services.mapper.system.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
@SpringBootTest
class MailingJobTest {
@Autowired
private EmailTemplateMapper emailTemplateMapper;
@Autowired
private EmailFactory emailFactory;
@Autowired
private BillMapper billMapper;
@Autowired
private UserMapper userMapper;
@Test
void executeWeekBillNotification() {
long userId = 1849444494908125181L;
String email = "q122l@freesourcecodes.com";
LocalDate today = LocalDate.now();
LocalDate startOfWeek = today.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY));
LocalDate endOfWeek = today.with(TemporalAdjusters.nextOrSame(DayOfWeek.SUNDAY));
// 查询当前用户信息
AdminUser adminUser = userMapper.selectById(userId);
// 查询验证码邮件模板
LambdaQueryWrapper<EmailTemplate> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(EmailTemplate::getIsDefault, true);
lambdaQueryWrapper.eq(EmailTemplate::getType, EmailTemplateEnums.BILL_WEEK_NOTIFICATION.getType());
EmailTemplate emailTemplate = emailTemplateMapper.selectOne(lambdaQueryWrapper);
// 查询当前用户账单数据
BillDto billDto = new BillDto();
billDto.setUserId(userId);
billDto.setStartDate(startOfWeek);
billDto.setEndDate(endOfWeek.plusDays(1));
List<BillVo> billVoList = billMapper.selectListByBillNotification(billDto);
// 发送账单日期
String sendDate = LocalDateTime.now().format(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD_HH_MM_SS));
String startDate = startOfWeek.format(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD));
String endDate = endOfWeek.format(DateTimeFormatter.ofPattern(LocalDateTimeConstant.YYYY_MM_DD));
// 计算总收入
double income = billVoList.stream()
.filter(billVo -> billVo.getType().equals(Byte.parseByte("1")))
.collect(Collectors.summarizingDouble(billVo -> billVo.getAmount().doubleValue()))
.getSum();
// 计算总支出
double expend = billVoList.stream()
.filter(billVo -> billVo.getType().equals(Byte.parseByte("-1")))
.collect(Collectors.summarizingDouble(billVo -> billVo.getAmount().doubleValue()))
.getSum();
// 计算盈利
double profit = income - expend;
// 计算盈利
double percentValue = (income + expend) == 0 ? 0 : expend / (income + expend);
BigDecimal percent = new BigDecimal(percentValue);
// 表格字符串
StringBuilder billTable = new StringBuilder();
billVoList.forEach(billVo -> {
String tr = " <td style=\"font-weight:bolder;\">" + billVo.getCategoryName() + "</td>\n" +
" <td style=\"color:#DC684A;font-weight:bolder;\">" + billVo.getAmount() + " ¥</td>\n" +
" <td style=\"color:#DC684A;font-weight:bolder;\">" + billVo.getDescription() + "</td>\n" +
"</tr>";
billTable.append(tr);
});
// 替换整理参数
HashMap<String, Object> hashMap = new HashMap<>();
hashMap.put("##billName##", adminUser.getNickname());
hashMap.put("##billDate##", startDate + "~" + endDate);
hashMap.put("##income##", income);
hashMap.put("##expend##", expend);
hashMap.put("##profit##", new BigDecimal(profit).setScale(2, RoundingMode.HALF_UP));
hashMap.put("##percent##", percent.setScale(2, RoundingMode.HALF_UP));
hashMap.put("##billTable##", billTable);
hashMap.put("##sendDate##", sendDate);
emailFactory.sendEmailTemplate(email, emailTemplate, hashMap);
}
}