新增功能
parent
a5adee3c5f
commit
e6d7a6db8e
9
pom.xml
9
pom.xml
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@
|
|||
<optional>true</optional> <!-- 表示依赖不会传递 -->
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- swagger3-->
|
||||
<dependency>
|
||||
<groupId>io.springfox</groupId>
|
||||
|
|
|
|||
|
|
@ -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() + " 封邮件");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
# 从数据源开关/默认关闭
|
||||
|
|
|
|||
|
|
@ -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/*
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -41,4 +41,5 @@ export function delNotice(noticeId) {
|
|||
url: '/system/notice/' + noticeId,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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({
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue