新增功能

pull/1120/head
dulujia 2025-12-03 21:51:53 +08:00
parent a5adee3c5f
commit e6d7a6db8e
12 changed files with 283 additions and 14 deletions

View File

@ -147,6 +147,15 @@
<artifactId>commons-io</artifactId>
<version>${commons.io.version}</version>
</dependency>
<!-- ruoyi-framework/pom.xml 里加(或 common 模块) -->
<!-- 方式一:显式指定版本 AI帮写-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- excel工具 -->
<dependency>

View File

@ -24,6 +24,7 @@
<optional>true</optional> <!-- 表示依赖不会传递 -->
</dependency>
<!-- swagger3-->
<dependency>
<groupId>io.springfox</groupId>

View File

@ -1,20 +1,15 @@
package com.ruoyi.web.controller.system;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.MailUtils;
import org.apache.commons.lang3.ArrayUtils;
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.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
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.*;
import org.springframework.web.multipart.MultipartFile;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
@ -53,6 +48,10 @@ public class SysUserController extends BaseController
@Autowired
private ISysPostService postService;
@Autowired
private MailUtils mailUtils;
/**
*
*/
@ -253,4 +252,84 @@ public class SysUserController extends BaseController
{
return success(deptService.selectDeptTreeList(dept));
}
/**
*
* type=1 userIds type=2 type=3 type=4
*/
@PreAuthorize("@ss.hasPermi('system:user:mail')")
@Log(title = "用户管理", businessType = BusinessType.OTHER)
@PostMapping("/mail/batch")
public AjaxResult sendMailBatch(@RequestBody Map<String, Object> requestData) {
// 从请求体中提取所有参数
String type = (String) requestData.get("type");
String subject = (String) requestData.get("subject");
String html = (String) requestData.get("html");
// 验证必需参数
if (StringUtils.isEmpty(type) || StringUtils.isEmpty(subject) || StringUtils.isEmpty(html)) {
return error("缺少必要参数");
}
// 提取可选参数
List<Long> userIds = null;
Long deptId = null;
SysUser queryUser = null;
if (requestData.containsKey("userIds")) {
userIds = (List<Long>) requestData.get("userIds");
}
if (requestData.containsKey("deptId")) {
Object deptIdObj = requestData.get("deptId");
if (deptIdObj instanceof Number) {
deptId = ((Number) deptIdObj).longValue();
} else {
deptId = Long.valueOf(deptIdObj.toString());
}
}
if (requestData.containsKey("queryUser")) {
Map<String, Object> userMap = (Map<String, Object>) requestData.get("queryUser");
queryUser = new SysUser();
// 根据需要设置queryUser的属性
if (userMap.containsKey("userName")) {
queryUser.setUserName((String) userMap.get("userName"));
}
if (userMap.containsKey("phonenumber")) {
queryUser.setPhonenumber((String) userMap.get("phonenumber"));
}
if (userMap.containsKey("status")) {
queryUser.setStatus((String) userMap.get("status"));
}
}
SysUser cond = new SysUser();
if ("1".equals(type) && userIds != null && !userIds.isEmpty()) {
cond.getParams().put("userIds", userIds);
} else if ("2".equals(type) && queryUser != null) {
cond = queryUser;
} else if ("3".equals(type) && deptId != null) {
cond.setDeptId(deptId);
} else if ("4".equals(type) && deptId != null) {
cond.getParams().put("roleId", deptId);
}
List<SysUser> users = userService.selectUserList(cond);
if (users.isEmpty()) {
return error("没有符合条件的用户");
}
List<String> mails = users.stream()
.filter(u -> StringUtils.isNotBlank(u.getEmail()))
.map(SysUser::getEmail)
.distinct()
.collect(Collectors.toList());
if (mails.isEmpty()) {
return error("这些用户都没有维护邮箱");
}
mailUtils.sendHtmlBatch(mails, subject, html);
return success("已发送 " + mails.size() + " 封邮件");
}
}

View File

@ -6,9 +6,9 @@ spring:
druid:
# 主库数据源
master:
url: jdbc:mysql://localhost:3306/ry-vue?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
url: jdbc:mysql://localhost:3306/ry1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
username: root
password: password
password: 123456
# 从库数据源
slave:
# 从数据源开关/默认关闭

View File

@ -47,6 +47,17 @@ user:
# Spring配置
spring:
mail:
host: smtp.163.com
port: 465
username: 17865799150@163.com # ← 你的邮箱
password: XGgv736G8saB6a23 # ← 下面步骤获取的「授权码」
protocol: smtps
properties:
mail.smtp.ssl.enable: true
mail.smtp.auth: true
mail.smtp.timeout: 5000
mail.smtp.connectiontimeout: 5000
# 资源信息
messages:
# 国际化资源文件路径
@ -72,7 +83,7 @@ spring:
# 端口默认为6379
port: 6379
# 数据库索引
database: 0
database: 10
# 密码
password:
# 连接超时时间
@ -88,6 +99,7 @@ spring:
# #连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# token配置
token:
# 令牌自定义标识
@ -134,3 +146,4 @@ xss:
excludes: /system/notice
# 匹配链接
urlPatterns: /system/*,/monitor/*,/tool/*

View File

@ -35,6 +35,13 @@
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 方式一:显式指定版本 AI帮写-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>

View File

@ -0,0 +1,59 @@
package com.ruoyi.common.utils;
import java.io.File;
import java.util.List;
import javax.mail.internet.MimeMessage;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class MailUtils {
@Resource
private JavaMailSender mailSender;
@Value("${spring.mail.username}")
private String from;
/** 纯文本 */
public void sendText(String to, String subject, String content) {
send(to, subject, content, false, null);
}
/** HTML 单发 */
public void sendHtml(String to, String subject, String html) {
send(to, subject, html, true, null);
}
/** HTML 群发(抄送模式,性能足够) */
public void sendHtmlBatch(List<String> toList, String subject, String html) {
if (toList == null || toList.isEmpty()) return;
String[] toArray = toList.toArray(new String[0]);
send(String.join(",", toArray), subject, html, true, null);
}
/** 带附件 */
public void sendWithFile(String to, String subject, String html, File file) {
send(to, subject, html, true, file);
}
/** 真正发送 */
private void send(String to, String subject, String text, boolean html, File file) {
try {
MimeMessage message = mailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8");
helper.setFrom(from);
helper.setTo(to.split(","));
helper.setSubject(subject);
helper.setText(text, html);
if (file != null) helper.addAttachment(file.getName(), file);
mailSender.send(message);
} catch (Exception e) {
throw new RuntimeException("邮件发送失败", e);
}
}
}

View File

@ -49,6 +49,7 @@
"vuex": "3.6.0"
},
"devDependencies": {
"@types/qs": "^6.14.0",
"@vue/cli-plugin-babel": "4.4.6",
"@vue/cli-service": "4.4.6",
"babel-plugin-dynamic-import-node": "2.3.3",

View File

@ -41,4 +41,5 @@ export function delNotice(noticeId) {
url: '/system/notice/' + noticeId,
method: 'delete'
})
}
}

View File

@ -1,3 +1,4 @@
import qs from 'qs'
import request from '@/utils/request'
import { parseStrEmpty } from "@/utils/ruoyi";
@ -10,6 +11,16 @@ export function listUser(query) {
})
}
export function sendMailBatch(data) {
return request({
url: '/system/user/mail/batch',
method: 'post',
data: data // 所有参数都在请求体中
})
}
// 查询用户详细
export function getUser(userId) {
return request({

View File

@ -56,7 +56,7 @@
</el-form>
<!-- 底部 -->
<div class="el-login-footer">
<span>Copyright © 2018-2025 ruoyi.vip All Rights Reserved.</span>
<span>你好你好请登录一下哦</span>
</div>
</div>
</template>

View File

@ -50,6 +50,12 @@
<el-col :span="1.5">
<el-button type="info" plain icon="el-icon-upload2" size="mini" @click="handleImport" v-hasPermi="['system:user:import']"></el-button>
</el-col>
<!-- 新增邮件发送前端代码 -->
<el-col :span="1.5">
<el-button type="text" icon="el-icon-message" @click="openBatchMail(1)"
:disabled="ids.length===0">群发邮件</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="el-icon-download" size="mini" @click="handleExport" v-hasPermi="['system:user:export']"></el-button>
</el-col>
@ -63,6 +69,7 @@
<el-table-column label="用户昵称" align="center" key="nickName" prop="nickName" v-if="columns.nickName.visible" :show-overflow-tooltip="true" />
<el-table-column label="部门" align="center" key="deptName" prop="dept.deptName" v-if="columns.deptName.visible" :show-overflow-tooltip="true" />
<el-table-column label="手机号码" align="center" key="phonenumber" prop="phonenumber" v-if="columns.phonenumber.visible" width="120" />
<el-table-column label="状态" align="center" key="status" v-if="columns.status.visible">
<template slot-scope="scope">
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
@ -178,6 +185,24 @@
<el-button @click="cancel"> </el-button>
</div>
</el-dialog>
<!-- 群发邮件 新增*****************************************-->
<el-dialog title="群发邮件" :visible.sync="batchMailOpen" width="600px" append-to-body>
<el-form label-width="70px">
<el-form-item label="邮件主题">
<el-input v-model="batchForm.subject" placeholder="请输入主题"/>
</el-form-item>
<el-form-item label="正文">
<Editor v-model="batchForm.html" :min-height="250"/>
</el-form-item>
<el-form-item label="变量提示">
<span style="color:#999;">支持 {userName} {deptName} 会自动替换</span>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="batchMailOpen=false"> </el-button>
<el-button type="primary" @click="submitBatchMail"> </el-button>
</div>
</el-dialog>
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body>
@ -198,10 +223,12 @@
</div>
</el-dialog>
</div>
</template>
<script>
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus, deptTreeSelect } from "@/api/system/user"
import { listUser, getUser, delUser, addUser, updateUser, resetUserPwd, changeUserStatus, deptTreeSelect ,sendMailBatch } from "@/api/system/user"/* 这里也加了一个引入 */
import { getToken } from "@/utils/auth"
import Treeselect from "@riophae/vue-treeselect"
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
@ -214,6 +241,7 @@ export default {
components: { Treeselect, Splitpanes, Pane },
data() {
return {
//
loading: true,
//
@ -267,6 +295,12 @@ export default {
//
url: process.env.VUE_APP_BASE_API + "/system/user/importData"
},
/* 新增变量**************************************************************** */
batchMailOpen: false, //
batchForm: { //
subject: '',
html: ''
},
//
queryParams: {
pageNum: 1,
@ -341,7 +375,61 @@ export default {
}
)
},
// ************************************
openBatchMail(type){
if(type===1 && this.ids.length===0){
this.$modal.msgWarning('请先勾选用户')
return
}
this.batchForm = { subject:'', html:'' }
this.batchForm.type = type
this.batchMailOpen = true
},
/* // 提交发送
submitBatchMail(){
const data = {
...this.batchForm,
userIds: this.batchForm.type===1 ? this.ids : undefined,
deptId: this.batchForm.type===3 ? this.queryParams.deptId : undefined,
queryUser: this.batchForm.type===2 ? this.queryParams : undefined
}
this.$modal.confirm('确认发送邮件?').then(()=>
sendMailBatch(data)
).then(res=>{
this.$modal.msgSuccess(res.msg)
this.batchMailOpen = false
})
}, */
//
//
submitBatchMail(){
const data = {
type: this.batchForm.type.toString(),
subject: this.batchForm.subject,
html: this.batchForm.html,
userIds: this.batchForm.type === 1 || this.batchForm.type === '1' ? this.ids : undefined,
deptId: this.batchForm.type === 3 || this.batchForm.type === '3' ? this.queryParams.deptId : undefined,
queryUser: this.batchForm.type === 2 || this.batchForm.type === '2' ? this.queryParams : undefined
}
if (!data.type || !data.subject || !data.html) {
this.$modal.msgError('请填写完整信息')
return
}
this.$modal.confirm('确认发送邮件?').then(() => {
return sendMailBatch(data)
}).then(res => {
this.$modal.msgSuccess(res.msg)
this.batchMailOpen = false
}).catch(err => {
this.$modal.msgError('发送失败: ' + (err.message || '未知错误'))
})
},
/** 查询部门下拉树结构 */
getDeptTree() {
deptTreeSelect().then(response => {
this.deptOptions = response.data