feat: 整改文档(补充生日模板配置)

This commit is contained in:
2026-09-24 11:50:14 +08:00
parent 29e871bd51
commit 34f37010ab
10 changed files with 329 additions and 31 deletions
@@ -1,13 +1,10 @@
package com.budwk.app.zhgh.dayofficework.birthdayWishes.task;
import cn.hutool.core.date.DateUtil;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import lombok.extern.slf4j.Slf4j;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.json.Json;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
@@ -22,18 +19,8 @@ public class BirthdayWishesTask implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
log.info("=================================执行计划任务:{}", context.getJobDetail().getKey().getName());
JobDataMap dataMap = context.getJobDetail().getJobDataMap();
log.info("=================================参数:{}", Json.toJson(dataMap));
String planName = dataMap.getString("name");
String today = DateUtil.today();
String title = planName + today;
String content = dataMap.getString("template");
// 统一由生日服务查询当天生日会员、生成移动端链接并记录系统推送日志。
int receiverCount = userBirthdayService.sendTodayBirthdayMessages(title, content);
int receiverCount = userBirthdayService.sendTodayBirthdayMessages();
log.info("=================================当天生日通知进入发送队列人数:{}", receiverCount);
}
}
@@ -0,0 +1,71 @@
package com.budwk.app.zhgh.staffmanage.birthday.controller;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.result.Result;
import com.budwk.app.zhgh.staffmanage.birthday.models.UserBirthdayConfig;
import com.budwk.app.zhgh.staffmanage.birthday.service.UserBirthdayService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.nutz.aop.interceptor.ioc.TransAop;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* 生日祝福配置管理。
* 配置内容包含页面图片、默认查询天数、自动发送开关和发送文案。
*/
@IocBean
@At("/platform/staffManage/birthday/config")
@Ok("json:full")
@Api(tags = "生日祝福配置")
public class UserBirthdayConfigController {
@Inject
private UserBirthdayService userBirthdayService;
@At("")
@Ok("beetl:/platform/zhgh/staffmanage/birthday/config/index.html")
@SaCheckPermission("staff.birthday.config")
public void index() {
}
/**
* 获取当前生日祝福配置。
*
* @return Result.data 为 UserBirthdayConfig,包含图片文件ID、查询天数、自动发送开关、标题和内容
*/
@At
@ApiOperation("获取生日祝福配置")
@SaCheckPermission("staff.birthday.config")
public Result getConfig() {
return Result.success(userBirthdayService.getConfig());
}
/**
* 保存生日祝福配置。
* config.picUrl、birthdayUrl 为图片文件ID;birthdayDays 为正整数;
* autoSendEnabled 为自动发送开关;sendTitle、sendContent 为默认发送文案。
*
* @return Result.data 为保存后的 UserBirthdayConfig
*/
@At
@Aop(TransAop.READ_COMMITTED)
@ApiOperation("保存生日祝福配置")
@SaCheckPermission("staff.birthday.config")
public Result saveConfig(UserBirthdayConfig config) {
if (config == null || config.getBirthdayDays() == null || config.getBirthdayDays() <= 0) {
return Result.error("默认查询天数必须为正整数");
}
if (StrUtil.isBlank(config.getSendTitle())) {
return Result.error("请输入发送标题");
}
if (StrUtil.isBlank(config.getSendContent())) {
return Result.error("请输入发送内容");
}
return Result.success("保存成功", userBirthdayService.saveConfig(config));
}
}
@@ -38,4 +38,24 @@ public class UserBirthdayConfig extends BaseModel {
@Comment("生日福利贺卡图片文件ID")
@ColDefine(type = ColType.VARCHAR, width = 100)
private String birthdayUrl;
@Column
@Comment("默认查询未来生日天数")
@ColDefine(type = ColType.INT)
private Integer birthdayDays;
@Column
@Comment("是否自动发送生日祝福")
@ColDefine(type = ColType.BOOLEAN)
private Boolean autoSendEnabled;
@Column
@Comment("默认发送标题")
@ColDefine(type = ColType.VARCHAR, width = 255)
private String sendTitle;
@Column
@Comment("默认发送内容")
@ColDefine(type = ColType.TEXT)
private String sendContent;
}
@@ -27,16 +27,17 @@ public class UserBirthdayPageForm extends MemberInfoPageForm {
/**
* 将生日日期条件追加到已有人员查询条件。
* 未传日期时默认查询今天起十五天内的生日,跨年时自动使用 OR 条件。
* 未传日期时按配置天数查询,配置无效时默认十五天,跨年时自动使用 OR 条件。
*
* @param cnd 已包含人员范围和会员状态的查询条件
*/
public void buildBirthdaySearch(Cnd cnd) {
public void buildBirthdaySearch(Cnd cnd, Integer birthdayDays) {
String start = normalizeMonthDay(startDate);
String end = normalizeMonthDay(endDate);
if (StrUtil.isAllBlank(start, end)) {
int days = birthdayDays == null || birthdayDays <= 0 ? 15 : birthdayDays;
start = DateUtil.format(DateUtil.date(), "MM-dd");
end = DateUtil.format(DateUtil.offsetDay(DateUtil.date(), 15), "MM-dd");
end = DateUtil.format(DateUtil.offsetDay(DateUtil.date(), days), "MM-dd");
}
if (StrUtil.isAllNotBlank(start, end)) {
@@ -53,21 +53,19 @@ public interface UserBirthdayService extends BaseService<Sys_user> {
/**
* 向当天生日的会员发送系统生日通知,供 Quartz 任务调用。
*
* @param title 消息标题
* @param content 消息正文
* @return 实际进入发送队列的接收人数
*/
int sendTodayBirthdayMessages(String title, String content);
int sendTodayBirthdayMessages();
/**
* 获取最新生日页面配置;未配置时返回空配置对象。
* 获取最新生日页面配置;未配置时返回默认配置。
*/
UserBirthdayConfig getConfig();
/**
* 新增或更新生日页面配置。
*
* @param config 背景图片和福利贺卡文件ID
* @param config 图片、查询天数、自动发送开关及默认消息内容
* @return 保存后的配置
*/
UserBirthdayConfig saveConfig(UserBirthdayConfig config);
@@ -25,12 +25,18 @@ import org.nutz.dao.Dao;
import org.nutz.dao.Sqls;
import org.nutz.dao.sql.Sql;
import org.nutz.ioc.aop.Aop;
import org.nutz.ioc.impl.PropertiesProxy;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.lang.Times;
import org.nutz.lang.util.NutMap;
import org.nutz.mvc.Mvcs;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
@@ -38,6 +44,13 @@ import java.util.List;
@IocBean(args = {"refer:dao"})
public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implements UserBirthdayService {
private static final int DEFAULT_BIRTHDAY_DAYS = 15;
private static final String DEFAULT_SEND_TITLE = "请点击查收您的生日福利";
private static final String DEFAULT_SEND_CONTENT = "校工会祝您:生日快乐!幸福安康";
@Inject
private PropertiesProxy conf;
@Inject
private SysMsgService sysMsgService;
@@ -106,7 +119,12 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
@Override
@Aop(TransAop.READ_COMMITTED)
public int sendTodayBirthdayMessages(String title, String content) {
public int sendTodayBirthdayMessages() {
UserBirthdayConfig config = getConfig();
if (!Boolean.TRUE.equals(config.getAutoSendEnabled())) {
log.info("生日祝福自动发送已关闭");
return 0;
}
Sql sql = Sqls.create("""
SELECT loginname
FROM vw_user
@@ -118,18 +136,37 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
sql.setCallback(Sqls.callback.strList());
dao().execute(sql);
List<String> loginNames = sql.getList(String.class);
return sendMessages(loginNames, title, content, "system", "系统推送", "系统推送");
return sendMessages(loginNames, config.getSendTitle(), config.getSendContent(),
"system", "系统推送", "系统推送");
}
@Override
public UserBirthdayConfig getConfig() {
UserBirthdayConfig config = dao().fetch(UserBirthdayConfig.class, Cnd.NEW().desc("updatedAt"));
return config == null ? new UserBirthdayConfig() : config;
return fillConfigDefaults(config == null ? new UserBirthdayConfig() : config);
}
@Override
@Aop(TransAop.READ_COMMITTED)
public UserBirthdayConfig saveConfig(UserBirthdayConfig config) {
UserBirthdayConfig current = dao().fetch(UserBirthdayConfig.class, Cnd.NEW().desc("updatedAt"));
if (current != null) {
config.setId(current.getId());
// 兼容原图片配置入口,未提交的新配置字段保留已有值。
if (config.getBirthdayDays() == null) {
config.setBirthdayDays(current.getBirthdayDays());
}
if (config.getAutoSendEnabled() == null) {
config.setAutoSendEnabled(current.getAutoSendEnabled());
}
if (config.getSendTitle() == null) {
config.setSendTitle(current.getSendTitle());
}
if (config.getSendContent() == null) {
config.setSendContent(current.getSendContent());
}
}
fillConfigDefaults(config);
dao().insertOrUpdate(config);
return config;
}
@@ -167,12 +204,51 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
cnd.and("u.birthday", "is not", null);
cnd.and("u.member", "=", 1);
pageForm.buildSearch(cnd, "u.");
pageForm.buildBirthdaySearch(cnd);
pageForm.buildBirthdaySearch(cnd, getConfig().getBirthdayDays());
cnd.asc("daysUntilBirthday");
sql.setCondition(cnd);
return sql;
}
private UserBirthdayConfig fillConfigDefaults(UserBirthdayConfig config) {
if (config.getBirthdayDays() == null || config.getBirthdayDays() <= 0) {
config.setBirthdayDays(DEFAULT_BIRTHDAY_DAYS);
}
if (config.getAutoSendEnabled() == null) {
config.setAutoSendEnabled(true);
}
config.setSendTitle(StrUtil.blankToDefault(config.getSendTitle(), DEFAULT_SEND_TITLE));
config.setSendContent(StrUtil.blankToDefault(config.getSendContent(), DEFAULT_SEND_CONTENT));
return config;
}
private String getBirthdayBaseUrl() {
HttpServletRequest request = Mvcs.getReq();
if (request != null) {
String requestUrl = request.getRequestURL().toString();
return requestUrl.substring(0, requestUrl.length() - request.getRequestURI().length())
+ request.getContextPath();
}
// 定时任务没有请求上下文;本机地址未配置端口时使用项目监听端口。
String baseUrl = StrUtil.removeSuffix(Globals.AppDomain, "/");
URI domain = URI.create(baseUrl);
if (domain.getPort() == -1 && StrUtil.equalsAnyIgnoreCase(domain.getHost(),
"localhost", "127.0.0.1", "[::1]", "::1")) {
int defaultPort = "https".equalsIgnoreCase(domain.getScheme()) ? 443 : 80;
int port = conf.getInt("server.port", defaultPort);
if (port != defaultPort) {
baseUrl = domain.getScheme() + "://" + domain.getRawAuthority() + ":" + port
+ StrUtil.blankToDefault(domain.getRawPath(), "");
}
}
String contextPath = StrUtil.removeSuffix(StrUtil.blankToDefault(Globals.AppBase, ""), "/");
if (StrUtil.isNotBlank(contextPath) && !baseUrl.endsWith(contextPath)) {
baseUrl += contextPath;
}
return baseUrl;
}
/**
* 将生日消息写入当前项目消息中心并生成发送记录。
* Sys_msg.url 用于站内消息点击,正文中的完整链接供外部短信/微信渠道访问。
@@ -190,9 +266,9 @@ public class UserBirthdayServiceImpl extends BaseServiceImpl<Sys_user> implement
UserBirthdayConfig config = getConfig();
String pagePath = "/platform/staffManage/birthday/manage/h5";
if (StrUtil.isNotBlank(config.getBirthdayUrl())) {
pagePath = pagePath + "?id=" + config.getBirthdayUrl();
pagePath = pagePath + "?id=" + URLEncoder.encode(config.getBirthdayUrl(), StandardCharsets.UTF_8);
}
String fullLink = Globals.AppDomain + pagePath;
String fullLink = getBirthdayBaseUrl() + pagePath;
Sys_msg message = new Sys_msg();
message.setTitle(title);
@@ -0,0 +1,6 @@
-- 已有生日配置表增加查询和发送配置字段。
ALTER TABLE `user_birthday_config`
ADD COLUMN `birthdayDays` int DEFAULT 15 COMMENT '默认查询未来生日天数',
ADD COLUMN `autoSendEnabled` tinyint(1) DEFAULT 1 COMMENT '是否自动发送生日祝福',
ADD COLUMN `sendTitle` varchar(255) DEFAULT NULL COMMENT '默认发送标题',
ADD COLUMN `sendContent` text COMMENT '默认发送内容';
@@ -2,6 +2,10 @@ CREATE TABLE IF NOT EXISTS `user_birthday_config` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`picUrl` varchar(100) DEFAULT NULL COMMENT '移动端背景图片文件ID',
`birthdayUrl` varchar(100) DEFAULT NULL COMMENT '生日福利贺卡图片文件ID',
`birthdayDays` int DEFAULT 15 COMMENT '默认查询未来生日天数',
`autoSendEnabled` tinyint(1) DEFAULT 1 COMMENT '是否自动发送生日祝福',
`sendTitle` varchar(255) DEFAULT NULL COMMENT '默认发送标题',
`sendContent` text COMMENT '默认发送内容',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
@@ -0,0 +1,122 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<div slot="header">
<span>生日祝福配置</span>
</div>
<el-form ref="configFormRef" :model="configForm" :rules="configRules"
label-width="150px" style="max-width:900px">
<el-form-item label="页面背景图片" prop="picUrl">
<file-upload :value.sync="configForm.picUrl" :upload_number="1" upload_mode="image"
accept=".jpg,.jpeg,.png" upload_result_category="interval"></file-upload>
<div class="text-muted">建议使用竖版生日主题图片。</div>
</el-form-item>
<el-form-item label="福利贺卡图片" prop="birthdayUrl">
<file-upload :value.sync="configForm.birthdayUrl" :upload_number="1" upload_mode="image"
accept=".jpg,.jpeg,.png" upload_result_category="interval"></file-upload>
<div class="text-muted">移动端点击“领取生日福利”后展示该图片。</div>
</el-form-item>
<el-form-item label="默认查询天数" prop="birthdayDays">
<el-input-number v-model="configForm.birthdayDays" :min="1"
controls-position="right"></el-input-number>
<span class="text-muted ml10">生日人员页未选择日期时,默认查询今天起未来N天。</span>
</el-form-item>
<!-- <el-form-item label="自动发送" prop="autoSendEnabled">
<el-switch v-model="configForm.autoSendEnabled"
active-text="开启" inactive-text="关闭"></el-switch>
</el-form-item>-->
<el-form-item label="发送标题" prop="sendTitle">
<el-input v-model="configForm.sendTitle" maxlength="255" show-word-limit
placeholder="请输入发送标题"></el-input>
</el-form-item>
<el-form-item label="发送内容" prop="sendContent">
<el-input v-model="configForm.sendContent" type="textarea" :rows="6"
placeholder="请输入发送内容"></el-input>
</el-form-item>
<el-form-item>
<el-button type="primary" :loading="formLoading" @click="saveConfig">保存配置</el-button>
<el-button @click="previewBirthday">预览生日页面</el-button>
</el-form-item>
</el-form>
</el-card>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
data() {
return {
formLoading: false,
configForm: {
id: "",
picUrl: "",
birthdayUrl: "",
birthdayDays: 15,
autoSendEnabled: true,
sendTitle: "请点击查收您的生日福利",
sendContent: "校工会祝您:生日快乐!幸福安康"
},
configRules: {
birthdayDays: [
{required:true,message:"请输入默认查询天数",trigger:["blur","change"]}
],
sendTitle: [
{required:true,message:"请输入发送标题",trigger:["blur","change"]}
],
sendContent: [
{required:true,message:"请输入发送内容",trigger:["blur","change"]}
]
}
}
},
methods: {
loadConfig() {
this.$set(this, "formLoading", true)
this.$axios.post(loc() + "/getConfig").then((res) => {
if (res.code === 0) {
this.$set(this, "configForm", Object.assign({}, this.configForm, res.data || {}))
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.$set(this, "formLoading", false)
})
},
saveConfig() {
this.$refs.configFormRef.validate((valid) => {
if (!valid) return
this.$confirm("确定保存生日祝福配置吗?", "提示", {type:"warning"}).then(() => {
this.$set(this, "formLoading", true)
this.$axios.post(loc() + "/saveConfig", this.configForm).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg)
this.$set(this, "configForm", res.data)
} else {
this.$message.warning(res.msg)
}
}).finally(() => {
this.$set(this, "formLoading", false)
})
}).catch(() => {})
})
},
previewBirthday() {
let url = "/platform/staffManage/birthday/manage/h5"
if (this.configForm.birthdayUrl) {
url = url + "?id=" + encodeURIComponent(this.configForm.birthdayUrl)
}
window.open(url, "_blank")
}
},
created() {
this.loadConfig()
}
})
</script>
<!--#
}
#-->
@@ -38,7 +38,7 @@ layout("/layouts/platform.html"){
</el-card>
<el-card shadow="never" class="mt10">
<table-tool label="生日人员(默认展示未来15天,日期查询忽略年份)"
<table-tool :label="'生日人员(默认展示未来' + birthdayDays + '天,日期查询忽略年份)'"
:columns.sync="tableColumns">
<el-button size="small" type="primary" @click="doExport">导出Excel</el-button>
<el-button size="small" type="primary" @click="openSendByQuery">发送通知</el-button>
@@ -172,6 +172,8 @@ layout("/layouts/platform.html"){
sendQueryForm: {title:"",content:""},
sendUserDialogVisible: false,
sendUserForm: {userId:"",userInfo:"",title:"",content:""},
birthdayDays: 15,
defaultMsgConfig: {title:"",content:""},
configDialogVisible: false,
configForm: {id:"",picUrl:"",birthdayUrl:""}
}
@@ -231,7 +233,7 @@ layout("/layouts/platform.html"){
})
},
openSendByQuery() {
this.$set(this, "sendQueryForm", Object.assign(this.requestParams(), {title:"",content:""}))
this.$set(this, "sendQueryForm", Object.assign(this.requestParams(), {title:this.defaultMsgConfig.title,content:this.defaultMsgConfig.content}))
this.$set(this, "sendQueryDialogVisible", true)
},
closeSendByQuery() {
@@ -259,7 +261,7 @@ layout("/layouts/platform.html"){
})
},
openSendByUser(row) {
this.$set(this, "sendUserForm", {userId:row.id,userInfo:row.username + "(" + row.loginname + ")",title:"",content:""})
this.$set(this, "sendUserForm", {userId:row.id,userInfo:row.username + "(" + row.loginname + ")",title:this.defaultMsgConfig.title,content:this.defaultMsgConfig.content})
this.$set(this, "sendUserDialogVisible", true)
},
closeSendByUser() {
@@ -286,6 +288,16 @@ layout("/layouts/platform.html"){
}).catch(() => {})
})
},
loadBirthdayConfig() {
this.$axios.post(loc() + "/getConfig").then((res) => {
if (res.code !== 0 || !res.data) return
this.$set(this, "birthdayDays", res.data.birthdayDays || 15)
this.$set(this, "defaultMsgConfig", {
title:res.data.sendTitle || "",
content:res.data.sendContent || ""
})
})
},
openConfig() {
this.$set(this, "formLoading", true)
this.$axios.post(loc() + "/getConfig").then((res) => {
@@ -321,6 +333,7 @@ layout("/layouts/platform.html"){
},
created() {
this.initOrganizationOptions()
this.loadBirthdayConfig()
this.pageData()
}
})