diff --git a/README.md b/README.md index 3f13356..ce26b47 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # 项目功能介绍 * 于若依管理系统 ruoyi-flowable-plus进行二次开发,基于Spring Boot、Mybatis-plus、Vue和ElementUI技术栈。 -* 工程项目管理系统适用于工程项目的投标管理及项目进度及成本控制用户的OA办公系统; +* 工程福安德综合办公系统适用于工程项目的投标管理及项目进度及成本控制用户的OA办公系统; * 流程管理:流程发起、我的流程、待办任务、已办任务; * 流程部署:在原有基础上增加了任务监听器,根据部门 id 流转到该部门的主管审批; * 项目管理:项目信息的维护,重点功能是项目的流程进度控制,根据项目进度类型填写任务进度信息; diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SocketContactController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SocketContactController.java new file mode 100644 index 0000000..c3f721a --- /dev/null +++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SocketContactController.java @@ -0,0 +1,108 @@ +package com.ruoyi.web.controller.system; + +import java.util.List; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +import lombok.RequiredArgsConstructor; +import javax.servlet.http.HttpServletResponse; +import javax.validation.constraints.*; +import cn.dev33.satoken.annotation.SaCheckPermission; +import org.springframework.web.bind.annotation.*; +import org.springframework.validation.annotation.Validated; +import com.ruoyi.common.annotation.RepeatSubmit; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.PageQuery; +import com.ruoyi.common.core.domain.R; +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import com.ruoyi.common.core.validate.QueryGroup; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.system.domain.vo.SocketContactVo; +import com.ruoyi.system.domain.bo.SocketContactBo; +import com.ruoyi.system.service.ISocketContactService; +import com.ruoyi.common.core.page.TableDataInfo; + +/** + * 通信目录 + * + * @author hdka + * @date 2024-10-21 + */ +@Validated +@RequiredArgsConstructor +@RestController +@RequestMapping("/system/contact") +public class SocketContactController extends BaseController { + + private final ISocketContactService iSocketContactService; + + /** + * 查询通信目录列表 + */ + @SaCheckPermission("system:contact:list") + @GetMapping("/list") + public TableDataInfo list(SocketContactBo bo, PageQuery pageQuery) { + return iSocketContactService.queryPageList(bo, pageQuery); + } + + /** + * 导出通信目录列表 + */ + @SaCheckPermission("system:contact:export") + @Log(title = "通信目录", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(SocketContactBo bo, HttpServletResponse response) { + List list = iSocketContactService.queryList(bo); + ExcelUtil.exportExcel(list, "通信目录", SocketContactVo.class, response); + } + + /** + * 获取通信目录详细信息 + * + * @param id 主键 + */ + @SaCheckPermission("system:contact:query") + @GetMapping("/{id}") + public R getInfo(@NotNull(message = "主键不能为空") + @PathVariable Long id) { + return R.ok(iSocketContactService.queryById(id)); + } + + /** + * 新增通信目录 + */ + @SaCheckPermission("system:contact:add") + @Log(title = "通信目录", businessType = BusinessType.INSERT) + @RepeatSubmit() + @PostMapping() + public R add(@Validated(AddGroup.class) @RequestBody SocketContactBo bo) { + return toAjax(iSocketContactService.insertByBo(bo)); + } + + /** + * 修改通信目录 + */ + @SaCheckPermission("system:contact:edit") + @Log(title = "通信目录", businessType = BusinessType.UPDATE) + @RepeatSubmit() + @PutMapping() + public R edit(@Validated(EditGroup.class) @RequestBody SocketContactBo bo) { + return toAjax(iSocketContactService.updateByBo(bo)); + } + + /** + * 删除通信目录 + * + * @param ids 主键串 + */ + @SaCheckPermission("system:contact:remove") + @Log(title = "通信目录", businessType = BusinessType.DELETE) + @DeleteMapping("/{ids}") + public R remove(@NotEmpty(message = "主键不能为空") + @PathVariable Long[] ids) { + return toAjax(iSocketContactService.deleteWithValidByIds(Arrays.asList(ids), true)); + } +} diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index fd5effd..cbcee68 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -27,7 +27,7 @@ captcha: # 开发环境配置 server: # 服务器的HTTP端口,默认为8080 - port: 8078 + port: 8080 servlet: # 应用的访问路径 context-path: / diff --git a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java index 627fa83..66703d6 100644 --- a/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java +++ b/ruoyi-extend/ruoyi-monitor-admin/src/main/java/com/ruoyi/monitor/admin/config/SecurityConfig.java @@ -34,6 +34,7 @@ public class SecurityConfig { , adminContextPath + "/login" , "/actuator" , "/actuator/**" + ,adminContextPath+"/websocket/**" ).permitAll() .anyRequest().authenticated() .and() diff --git a/ruoyi-framework/pom.xml b/ruoyi-framework/pom.xml index cf1f6c0..b460b8f 100644 --- a/ruoyi-framework/pom.xml +++ b/ruoyi-framework/pom.xml @@ -18,15 +18,15 @@ - + org.springframework.boot spring-boot-starter-web - - - spring-boot-starter-tomcat - org.springframework.boot - - + + + spring-boot-starter-tomcat + org.springframework.boot + + @@ -67,6 +67,17 @@ ruoyi-common + + org.springframework.boot + spring-boot-starter-websocket + 2.0.4.RELEASE + + + com.alibaba.fastjson2 + fastjson2 + 2.0.43 + compile + diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/MessageType.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/MessageType.java new file mode 100644 index 0000000..c06f7da --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/MessageType.java @@ -0,0 +1,25 @@ +package com.ruoyi.framework.webSocket; + +/** + * 消息类型 + */ +public enum MessageType { + + SYS("sys", "系统消息"), CHAT("chat", "聊天消息"); + + private String type; + private String value; + + private MessageType(String type, String value) { + this.type = type; + this.value = value; + } + + public String getType() { + return type; + } + + public String getValue() { + return value; + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/WebSocketConfig.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/WebSocketConfig.java new file mode 100644 index 0000000..2226f37 --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/WebSocketConfig.java @@ -0,0 +1,19 @@ +package com.ruoyi.framework.webSocket; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.socket.server.standard.ServerEndpointExporter; + +/** + * websocket 配置 + * + */ +@Configuration +public class WebSocketConfig +{ + @Bean + public ServerEndpointExporter serverEndpointExporter() + { + return new ServerEndpointExporter(); + } +} \ No newline at end of file diff --git a/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/WebSocketServer.java b/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/WebSocketServer.java new file mode 100644 index 0000000..f79c5fa --- /dev/null +++ b/ruoyi-framework/src/main/java/com/ruoyi/framework/webSocket/WebSocketServer.java @@ -0,0 +1,128 @@ +package com.ruoyi.framework.webSocket; + +import com.alibaba.fastjson2.JSONObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import javax.websocket.*; +import javax.websocket.server.PathParam; +import javax.websocket.server.ServerEndpoint; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +@Component +@ServerEndpoint("/websocket/{userId}") +public class WebSocketServer { + private static final Logger log = LoggerFactory.getLogger(WebSocketServer.class); + + //静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。 + private static AtomicInteger onlineNum = new AtomicInteger(); + + //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。 + private static ConcurrentHashMap sessionPools = new ConcurrentHashMap<>(); + + /** + * 线程安全list,用来存放 在线客户端账号 + */ + public static List userList = new CopyOnWriteArrayList<>(); + + + /** + * 连接成功 + * @param session + * @param userId + */ + @OnOpen + public void onOpen(Session session, @PathParam(value = "userId") String userId) { + sessionPools.put(userId, session); + if (!userList.contains(userId)) { + addOnlineCount(); + userList.add(userId); + } + log.debug("ID为【" + userId + "】的用户加入websocket!当前在线人数为:" + onlineNum); + log.debug("当前在线:" + userList); + } + + /** + * 关闭连接 + * @param userId + */ + @OnClose + public void onClose(@PathParam(value = "userId") String userId) { + sessionPools.remove(userId); + if (userList.contains(userId)) { + userList.remove(userId); + subOnlineCount(); + } + log.debug(userId + "断开webSocket连接!当前人数为" + onlineNum); + + } + + /** + * 消息监听 + * @param message + * @throws IOException + */ + @OnMessage + public void onMessage(String message) throws IOException { + JSONObject jsonObject = JSONObject.parseObject(message); + String userId = jsonObject.getString("userId"); + String type = jsonObject.getString("type"); + if (type.equals(MessageType.CHAT.getType())) { + log.debug("聊天消息推送"); + sendToUser(userId, JSONObject.toJSONString(jsonObject)); + } + } + + /** + * 连接错误 + * @param session + * @param throwable + * @throws IOException + */ + @OnError + public void onError(Session session, Throwable throwable) throws IOException { + log.error("websocket连接错误!"); + throwable.printStackTrace(); + } + + /** + * 发送消息 + */ + public void sendMessage(Session session, String message) throws IOException, EncodeException { + if (session != null) { + synchronized (session) { + session.getBasicRemote().sendText(message); + } + } + } + + /** + * 给指定用户发送信息 + */ + public void sendToUser(String userId, String message) { + Session session = sessionPools.get(userId); + try { + if (session != null) { + sendMessage(session, message); + }else { + log.debug("推送用户不在线"); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void addOnlineCount() { + onlineNum.incrementAndGet(); + } + + public static void subOnlineCount() { + onlineNum.decrementAndGet(); + + } +} \ No newline at end of file diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/SocketContact.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SocketContact.java new file mode 100644 index 0000000..1766c93 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/SocketContact.java @@ -0,0 +1,48 @@ +package com.ruoyi.system.domain; + +import com.baomidou.mybatisplus.annotation.*; +import lombok.Data; +import lombok.EqualsAndHashCode; +import java.io.Serializable; +import java.util.Date; +import java.math.BigDecimal; + +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 通信目录对象 socket_contact + * + * @author hdka + * @date 2024-10-21 + */ +@Data +@EqualsAndHashCode(callSuper = true) +@TableName("socket_contact") +public class SocketContact extends BaseEntity { + + private static final long serialVersionUID=1L; + + /** + * 通信id + */ + @TableId(value = "id") + private Long id; + /** + * 用户id + */ + private Long contactUserId; + /** + * 发送者 + */ + private Long userId; + /** + * 删除标志 + */ + @TableLogic + private Long delFlag; + /** + * 备注 + */ + private String remark; + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SocketContactBo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SocketContactBo.java new file mode 100644 index 0000000..8ab9955 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/bo/SocketContactBo.java @@ -0,0 +1,52 @@ +package com.ruoyi.system.domain.bo; + +import com.ruoyi.common.core.validate.AddGroup; +import com.ruoyi.common.core.validate.EditGroup; +import lombok.Data; +import lombok.EqualsAndHashCode; +import javax.validation.constraints.*; + +import java.util.Date; + +import com.ruoyi.common.core.domain.BaseEntity; + +/** + * 通信目录业务对象 socket_contact + * + * @author hdka + * @date 2024-10-21 + */ + +@Data +@EqualsAndHashCode(callSuper = true) +public class SocketContactBo extends BaseEntity { + + /** + * 通信id + */ + @NotNull(message = "通信id不能为空", groups = { EditGroup.class }) + private Long id; + + /** + * 用户id + */ + @NotNull(message = "用户id不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long contactUserId; + + /** + * 发送者 + */ + @NotNull(message = "发送者不能为空", groups = { AddGroup.class, EditGroup.class }) + private Long userId; + + /** + * 备注 + */ + @NotBlank(message = "备注不能为空", groups = { AddGroup.class, EditGroup.class }) + private String remark; + + /** 用户名 */ + private String userName; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SocketContactVo.java b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SocketContactVo.java new file mode 100644 index 0000000..e602cd3 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/domain/vo/SocketContactVo.java @@ -0,0 +1,55 @@ +package com.ruoyi.system.domain.vo; + +import com.alibaba.excel.annotation.ExcelIgnoreUnannotated; +import com.alibaba.excel.annotation.ExcelProperty; +import com.ruoyi.common.annotation.ExcelDictFormat; +import com.ruoyi.common.convert.ExcelDictConvert; +import com.ruoyi.common.core.domain.entity.SysUser; +import lombok.Data; +import java.util.Date; + + + +/** + * 通信目录视图对象 socket_contact + * + * @author hdka + * @date 2024-10-21 + */ +@Data +@ExcelIgnoreUnannotated +public class SocketContactVo { + + private static final long serialVersionUID = 1L; + + /** + * 通信id + */ + @ExcelProperty(value = "通信id") + private Long id; + + /** + * 用户id + */ + @ExcelProperty(value = "用户id") + private Long contactUserId; + + /** + * 发送者 + */ + @ExcelProperty(value = "发送者") + private Long userId; + + /** + * 备注 + */ + @ExcelProperty(value = "备注") + private String remark; + + /** + * user对象 + */ + private SysUser user; + + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SocketContactMapper.java b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SocketContactMapper.java new file mode 100644 index 0000000..095fc84 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/mapper/SocketContactMapper.java @@ -0,0 +1,15 @@ +package com.ruoyi.system.mapper; + +import com.ruoyi.system.domain.SocketContact; +import com.ruoyi.system.domain.vo.SocketContactVo; +import com.ruoyi.common.core.mapper.BaseMapperPlus; + +/** + * 通信目录Mapper接口 + * + * @author hdka + * @date 2024-10-21 + */ +public interface SocketContactMapper extends BaseMapperPlus { + +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/ISocketContactService.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISocketContactService.java new file mode 100644 index 0000000..2eefcf7 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/ISocketContactService.java @@ -0,0 +1,49 @@ +package com.ruoyi.system.service; + +import com.ruoyi.system.domain.SocketContact; +import com.ruoyi.system.domain.vo.SocketContactVo; +import com.ruoyi.system.domain.bo.SocketContactBo; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.domain.PageQuery; + +import java.util.Collection; +import java.util.List; + +/** + * 通信目录Service接口 + * + * @author hdka + * @date 2024-10-21 + */ +public interface ISocketContactService { + + /** + * 查询通信目录 + */ + SocketContactVo queryById(Long id); + + /** + * 查询通信目录列表 + */ + TableDataInfo queryPageList(SocketContactBo bo, PageQuery pageQuery); + + /** + * 查询通信目录列表 + */ + List queryList(SocketContactBo bo); + + /** + * 新增通信目录 + */ + Boolean insertByBo(SocketContactBo bo); + + /** + * 修改通信目录 + */ + Boolean updateByBo(SocketContactBo bo); + + /** + * 校验并批量删除通信目录信息 + */ + Boolean deleteWithValidByIds(Collection ids, Boolean isValid); +} diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SocketContactServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SocketContactServiceImpl.java new file mode 100644 index 0000000..52d4063 --- /dev/null +++ b/ruoyi-system/src/main/java/com/ruoyi/system/service/impl/SocketContactServiceImpl.java @@ -0,0 +1,121 @@ +package com.ruoyi.system.service.impl; + +import cn.hutool.core.bean.BeanUtil; +import com.ruoyi.common.core.domain.entity.SysUser; +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.domain.PageQuery; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.ruoyi.system.mapper.SysUserMapper; +import com.ruoyi.system.service.ISysUserService; +import liquibase.pro.packaged.S; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import com.ruoyi.system.domain.bo.SocketContactBo; +import com.ruoyi.system.domain.vo.SocketContactVo; +import com.ruoyi.system.domain.SocketContact; +import com.ruoyi.system.mapper.SocketContactMapper; +import com.ruoyi.system.service.ISocketContactService; + +import java.util.List; +import java.util.Map; +import java.util.Collection; + +/** + * 通信目录Service业务层处理 + * + * @author hdka + * @date 2024-10-21 + */ +@RequiredArgsConstructor +@Service +public class SocketContactServiceImpl implements ISocketContactService { + + private final SocketContactMapper baseMapper; + + private final SysUserMapper sysUserMapper; + + /** + * 查询通信目录 + */ + @Override + public SocketContactVo queryById(Long id){ + return baseMapper.selectVoById(id); + } + + /** + * 查询通信目录列表 + */ + @Override + public TableDataInfo queryPageList(SocketContactBo bo, PageQuery pageQuery) { + LambdaQueryWrapper socketContactLambdaQueryWrapper = buildQueryWrapper(bo); + Page result = baseMapper.selectVoPage(pageQuery.build(), socketContactLambdaQueryWrapper); + for (SocketContactVo record : result.getRecords()) { + SysUser sysUser = sysUserMapper.selectUserById(record.getContactUserId()); + record.setUser(sysUser); + } + return TableDataInfo.build(result); + } + + /** + * 查询通信目录列表 + */ + @Override + public List queryList(SocketContactBo bo) { + LambdaQueryWrapper lqw = buildQueryWrapper(bo); + return baseMapper.selectVoList(lqw); + } + + private LambdaQueryWrapper buildQueryWrapper(SocketContactBo bo) { + Map params = bo.getParams(); + LambdaQueryWrapper lqw = Wrappers.lambdaQuery(); + lqw.eq(bo.getContactUserId() != null, SocketContact::getContactUserId, bo.getContactUserId()); + lqw.eq(bo.getUserId() != null, SocketContact::getUserId, bo.getUserId()); + return lqw; + } + + /** + * 新增通信目录 + */ + @Override + public Boolean insertByBo(SocketContactBo bo) { + SocketContact add = BeanUtil.toBean(bo, SocketContact.class); + validEntityBeforeSave(add); + boolean flag = baseMapper.insert(add) > 0; + if (flag) { + bo.setId(add.getId()); + } + return flag; + } + + /** + * 修改通信目录 + */ + @Override + public Boolean updateByBo(SocketContactBo bo) { + SocketContact update = BeanUtil.toBean(bo, SocketContact.class); + validEntityBeforeSave(update); + return baseMapper.updateById(update) > 0; + } + + /** + * 保存前的数据校验 + */ + private void validEntityBeforeSave(SocketContact entity){ + //TODO 做一些数据校验,如唯一约束 + } + + /** + * 批量删除通信目录 + */ + @Override + public Boolean deleteWithValidByIds(Collection ids, Boolean isValid) { + if(isValid){ + //TODO 做一些业务上的校验,判断是否需要校验 + } + return baseMapper.deleteBatchIds(ids) > 0; + } +} diff --git a/ruoyi-ui/.env.development b/ruoyi-ui/.env.development index 7a09b37..3285d81 100644 --- a/ruoyi-ui/.env.development +++ b/ruoyi-ui/.env.development @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 工程项目进度及成本控制管理系统 +VUE_APP_TITLE = 福安德综合办公系统 # 开发环境配置 ENV = 'development' @@ -13,6 +13,10 @@ VUE_APP_CONTEXT_PATH = '/' # 监控地址 VUE_APP_MONITRO_ADMIN = 'http://localhost:9090/admin/login' + +# websocket服务地址 +VUE_APP_SOCKET_SERVER = 'ws://localhost:8080/websocket/' + # xxl-job 控制台地址 VUE_APP_XXL_JOB_ADMIN = 'http://localhost:9100/xxl-job-admin' diff --git a/ruoyi-ui/.env.production b/ruoyi-ui/.env.production index aa1b3a2..e89ddb8 100644 --- a/ruoyi-ui/.env.production +++ b/ruoyi-ui/.env.production @@ -1,5 +1,5 @@ # 页面标题 -VUE_APP_TITLE = 工程项目进度及成本控制管理系统 +VUE_APP_TITLE = 福安德综合办公系统 # 生产环境配置 ENV = 'production' @@ -13,6 +13,9 @@ VUE_APP_CONTEXT_PATH = '/' # 监控地址 VUE_APP_MONITRO_ADMIN = '/admin/login' +# websocket服务地址 +VUE_APP_SOCKET_SERVER = 'ws://localhost:8080/websocket/' + # xxl-job 控制台地址 VUE_APP_XXL_JOB_ADMIN = '/xxl-job-admin' diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index 8af995d..c53f2fc 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -53,6 +53,7 @@ "quill": "1.3.7", "screenfull": "5.0.2", "sortablejs": "1.10.2", + "v-emoji-picker": "^2.3.3", "vue": "2.6.12", "vue-count-to": "1.0.13", "vue-cropper": "0.5.5", diff --git a/ruoyi-ui/src/api/system/contact.js b/ruoyi-ui/src/api/system/contact.js new file mode 100644 index 0000000..0a092ee --- /dev/null +++ b/ruoyi-ui/src/api/system/contact.js @@ -0,0 +1,44 @@ +import request from '@/utils/request' + +// 查询通信目录列表 +export function listContact(query) { + return request({ + url: '/system/contact/list', + method: 'get', + params: query + }) +} + +// 查询通信目录详细 +export function getContact(id) { + return request({ + url: '/system/contact/' + id, + method: 'get' + }) +} + +// 新增通信目录 +export function addContact(data) { + return request({ + url: '/system/contact', + method: 'post', + data: data + }) +} + +// 修改通信目录 +export function updateContact(data) { + return request({ + url: '/system/contact', + method: 'put', + data: data + }) +} + +// 删除通信目录 +export function delContact(id) { + return request({ + url: '/system/contact/' + id, + method: 'delete' + }) +} diff --git a/ruoyi-ui/src/api/system/message.js b/ruoyi-ui/src/api/system/message.js new file mode 100644 index 0000000..e69de29 diff --git a/ruoyi-ui/src/assets/images/contact.png b/ruoyi-ui/src/assets/images/contact.png new file mode 100644 index 0000000..b6af4f7 Binary files /dev/null and b/ruoyi-ui/src/assets/images/contact.png differ diff --git a/ruoyi-ui/src/assets/images/emoji.png b/ruoyi-ui/src/assets/images/emoji.png new file mode 100644 index 0000000..af702df Binary files /dev/null and b/ruoyi-ui/src/assets/images/emoji.png differ diff --git a/ruoyi-ui/src/assets/images/message.png b/ruoyi-ui/src/assets/images/message.png new file mode 100644 index 0000000..4f15c90 Binary files /dev/null and b/ruoyi-ui/src/assets/images/message.png differ diff --git a/ruoyi-ui/src/layout/components/Sidebar/Logo.vue b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue index adafa7e..5acb505 100644 --- a/ruoyi-ui/src/layout/components/Sidebar/Logo.vue +++ b/ruoyi-ui/src/layout/components/Sidebar/Logo.vue @@ -35,7 +35,7 @@ export default { }, data() { return { - title: '项目管理系统', + title: '福安德综合办公系统', logo: logoImg } } diff --git a/ruoyi-ui/src/main.js b/ruoyi-ui/src/main.js index a8ef984..3cedcf3 100644 --- a/ruoyi-ui/src/main.js +++ b/ruoyi-ui/src/main.js @@ -40,6 +40,8 @@ import VueMeta from 'vue-meta' // 字典数据组件 import DictData from '@/components/DictData' +import webSocket from "./utils/websocket"; + //打印组件 添加时间:2024年3月9日 import Print from 'vue-print-nb' @@ -55,6 +57,7 @@ Vue.prototype.selectDictLabel = selectDictLabel Vue.prototype.selectDictLabels = selectDictLabels Vue.prototype.download = download Vue.prototype.handleTree = handleTree +Vue.prototype.$webSocket = webSocket // 全局组件挂载 Vue.component('DictTag', DictTag) @@ -91,9 +94,32 @@ Vue.use(Element, { Vue.config.productionTip = false -new Vue({ +let newVue = new Vue({ el: '#app', + created() { + //监听用户窗口是否关闭 + window.addEventListener('beforeunload', this.closeSocket); + }, + destroyed() { + window.removeEventListener('beforeunload', this.closeSocket); + }, + methods: { + onBeforeUnload(event) { + // 在这里编写你想要执行的代码 + // 例如:发送数据到服务器或者显示警告信息 + // 设置event.returnValue以显示浏览器默认的警告信息 + event.returnValue = '您可能有未保存的更改!'; + }, + closeSocket() { + //关闭websocket连接 + this.$websocket.close(); + } + }, router, store, render: h => h(App) }) + + + +export default newVue diff --git a/ruoyi-ui/src/store/modules/user.js b/ruoyi-ui/src/store/modules/user.js index d67c9a4..01389f1 100644 --- a/ruoyi-ui/src/store/modules/user.js +++ b/ruoyi-ui/src/store/modules/user.js @@ -1,6 +1,6 @@ import { login, logout, getInfo } from '@/api/login' import { getToken, setToken, removeToken } from '@/utils/auth' - +import newVue from '@/main' const user = { state: { token: getToken(), @@ -25,7 +25,10 @@ const user = { }, SET_PERMISSIONS: (state, permissions) => { state.permissions = permissions - } + }, + SET_ID: (state, id) => { + state.id = id + }, }, actions: { @@ -51,6 +54,7 @@ const user = { return new Promise((resolve, reject) => { getInfo().then(res => { const user = res.data.user + console.log(user) const avatar = (user.avatar == "" || user.avatar == null) ? require("@/assets/images/profile.jpg") : user.avatar; if (res.data.roles && res.data.roles.length > 0) { // 验证返回的roles是否是一个非空数组 commit('SET_ROLES', res.data.roles) @@ -58,8 +62,11 @@ const user = { } else { commit('SET_ROLES', ['ROLE_DEFAULT']) } + commit('SET_ID',user.userId) commit('SET_NAME', user.userName) commit('SET_AVATAR', avatar) + // TODO 获取用户信息时检查socket连接状态并进行连接 + newVue.$webSocket.initWebSocket() resolve(res) }).catch(error => { reject(error) @@ -75,6 +82,8 @@ const user = { commit('SET_ROLES', []) commit('SET_PERMISSIONS', []) removeToken() + // TODO 用户推出登陆后 关闭socket连接 + newVue.$webSocket.close() resolve() }).catch(error => { reject(error) diff --git a/ruoyi-ui/src/utils/websocket.js b/ruoyi-ui/src/utils/websocket.js new file mode 100644 index 0000000..60e8c6b --- /dev/null +++ b/ruoyi-ui/src/utils/websocket.js @@ -0,0 +1,123 @@ +import { Notification } from "element-ui"; +import { getToken } from "./auth"; +import store from '../store' + +var socket = null;//实例对象 +var lockReconnect = false; //是否真正建立连接 +var timeout = 20 * 1000; //20秒一次心跳 +var timeoutObj = null; //心跳倒计时 +var serverTimeoutObj = null; //服务心跳倒计时 +var timeoutnum = null; //断开 重连倒计时 + +const initWebSocket = async () => { + if ("WebSocket" in window) { + if (!store.state.user.id) { + console.log("未登录!websocket工具获取不到userId") + }else { + const wsUrl = process.env.VUE_APP_SOCKET_SERVER + store.state.user.id; + console.log("连接已开启") + + socket = new WebSocket(wsUrl); + + socket.onerror = webSocketOnError; + socket.onmessage = webSocketOnMessage; + socket.onclose = closeWebsocket; + socket.onopen = openWebsocket; + console.log(socket) + } + } else { + Notification.error({ + title: "错误", + message: "您的浏览器不支持websocket,请更换Chrome或者Firefox", + }); + } +} + +//建立连接 +const openWebsocket = (e) => { + start(); +} + +const start = ()=> { + //开启心跳 + timeoutObj && clearTimeout(timeoutObj); + serverTimeoutObj && clearTimeout(serverTimeoutObj); + timeoutObj = setTimeout(function() { + //这里发送一个心跳,后端收到后,返回一个心跳消息 + if (socket.readyState == 1) { + //如果连接正常 + // socket.send("heartbeat"); + } else { + //否则重连 + reconnect(); + } + serverTimeoutObj = setTimeout(function() { + //超时关闭 + socket.close(); + }, timeout); + }, timeout); +} + +//重新连接 +const reconnect =() => { + if (lockReconnect) { + return; + } + lockReconnect = true; + //没连接上会一直重连,设置延迟避免请求过多 + timeoutnum && clearTimeout(timeoutnum); + timeoutnum = setTimeout(function() { + //新连接 + initWebSocket(); + lockReconnect = false; + }, 1000); +} + +//重置心跳 +const reset =() => { + //清除时间 + clearTimeout(timeoutObj); + clearTimeout(serverTimeoutObj); + //重启心跳 + start(); +} + +const sendWebsocket = (message) =>{ + socket.send(message); +} + +const webSocketOnError = (e) => { + initWebSocket(); + reconnect(); + +} + +//服务器返回的数据 +const webSocketOnMessage = (e)=> { + //判断是否登录 + console.log("111111111111"+e) + if (getToken()) { + //window自定义事件 + window.dispatchEvent( + new CustomEvent("onmessageWS", { + detail: { + data: JSON.parse(e?.data) + }, + }) + ); + } + // socket.onmessage(e) + reset(); +} + +const closeWebsocket=(e) => { + reconnect(); +} + +//断开连接 +const close =() => { +//WebSocket对象也有发送和关闭的两个方法,只需要在自定义方法中分别调用send()和close()即可实现。 + socket.close(); +} +//具体问题具体分析,把需要用到的方法暴露出去 +export default { initWebSocket, sendWebsocket, webSocketOnMessage, close }; diff --git a/ruoyi-ui/src/views/login.vue b/ruoyi-ui/src/views/login.vue index fb073a3..a87ca95 100644 --- a/ruoyi-ui/src/views/login.vue +++ b/ruoyi-ui/src/views/login.vue @@ -1,7 +1,7 @@ diff --git a/ruoyi-ui/src/views/oa/webSocket/index.vue b/ruoyi-ui/src/views/oa/webSocket/index.vue new file mode 100644 index 0000000..3f06e02 --- /dev/null +++ b/ruoyi-ui/src/views/oa/webSocket/index.vue @@ -0,0 +1,361 @@ + + + + + diff --git a/ruoyi-ui/src/views/register.vue b/ruoyi-ui/src/views/register.vue index 50d8b92..1a0fc77 100644 --- a/ruoyi-ui/src/views/register.vue +++ b/ruoyi-ui/src/views/register.vue @@ -1,7 +1,7 @@ diff --git a/ruoyi-ui/vue.config.js b/ruoyi-ui/vue.config.js index be8afd4..a580e18 100644 --- a/ruoyi-ui/vue.config.js +++ b/ruoyi-ui/vue.config.js @@ -7,7 +7,7 @@ function resolve(dir) { const CompressionPlugin = require('compression-webpack-plugin') -const name = process.env.VUE_APP_TITLE || '工程项目进度及成本控制管理系统' // 网页标题 +const name = process.env.VUE_APP_TITLE || '福安德综合办公系统' // 网页标题 const port = process.env.port || process.env.npm_config_port || 80 // 端口 @@ -35,7 +35,7 @@ module.exports = { proxy: { // detail: https://cli.vuejs.org/config/#devserver-proxy [process.env.VUE_APP_BASE_API]: { - target: `http://localhost:8078`, + target: `http://localhost:8080`, changeOrigin: true, pathRewrite: { ['^' + process.env.VUE_APP_BASE_API]: ''