mirror of
				https://github.com/continew-org/continew-admin.git
				synced 2025-10-31 22:57:17 +08:00 
			
		
		
		
	refactor: 优化站内信及消息管理
1.新增站内信未读消息轮询 2.优化消息管理 API,移除部分无用 API 3.优化部分代码格式
This commit is contained in:
		| @@ -67,9 +67,4 @@ public class SysConsts { | |||||||
|      * VO 描述类字段后缀 |      * VO 描述类字段后缀 | ||||||
|      */ |      */ | ||||||
|     public static final String VO_DESCRIPTION_FIELD_SUFFIX = "String"; |     public static final String VO_DESCRIPTION_FIELD_SUFFIX = "String"; | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 系统消息类型 |  | ||||||
|      */ |  | ||||||
|     public static final String SYSTEM_MESSAGE_TYPE = "1"; |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,41 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.charles7c.cnadmin.common.enums; | ||||||
|  |  | ||||||
|  | import lombok.Getter; | ||||||
|  | import lombok.RequiredArgsConstructor; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.base.IBaseEnum; | ||||||
|  | import top.charles7c.cnadmin.common.constant.UIConsts; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 消息类型枚举 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/11/2 20:08 | ||||||
|  |  */ | ||||||
|  | @Getter | ||||||
|  | @RequiredArgsConstructor | ||||||
|  | public enum MessageTypeEnum implements IBaseEnum<Integer> { | ||||||
|  |  | ||||||
|  |     /** 系统消息 */ | ||||||
|  |     SYSTEM(1, "系统消息", UIConsts.COLOR_PRIMARY),; | ||||||
|  |  | ||||||
|  |     private final Integer value; | ||||||
|  |     private final String description; | ||||||
|  |     private final String color; | ||||||
|  | } | ||||||
| @@ -28,10 +28,10 @@ import cn.hutool.core.bean.BeanUtil; | |||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
| import cn.hutool.core.lang.tree.Tree; | import cn.hutool.core.lang.tree.Tree; | ||||||
| import cn.hutool.core.lang.tree.TreeNodeConfig; | import cn.hutool.core.lang.tree.TreeNodeConfig; | ||||||
| import cn.hutool.core.map.MapUtil; |  | ||||||
| import cn.hutool.core.util.IdUtil; | import cn.hutool.core.util.IdUtil; | ||||||
| import cn.hutool.core.util.RandomUtil; | import cn.hutool.core.util.RandomUtil; | ||||||
| import cn.hutool.core.util.ReUtil; | import cn.hutool.core.util.ReUtil; | ||||||
|  | import cn.hutool.core.util.StrUtil; | ||||||
| import cn.hutool.json.JSONUtil; | import cn.hutool.json.JSONUtil; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.auth.model.vo.MetaVO; | import top.charles7c.cnadmin.auth.model.vo.MetaVO; | ||||||
| @@ -45,6 +45,7 @@ import top.charles7c.cnadmin.common.constant.SysConsts; | |||||||
| import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | import top.charles7c.cnadmin.common.enums.DisEnableStatusEnum; | ||||||
| import top.charles7c.cnadmin.common.enums.GenderEnum; | import top.charles7c.cnadmin.common.enums.GenderEnum; | ||||||
| import top.charles7c.cnadmin.common.enums.MenuTypeEnum; | import top.charles7c.cnadmin.common.enums.MenuTypeEnum; | ||||||
|  | import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||||
| import top.charles7c.cnadmin.common.model.dto.LoginUser; | import top.charles7c.cnadmin.common.model.dto.LoginUser; | ||||||
| import top.charles7c.cnadmin.common.util.SecureUtils; | import top.charles7c.cnadmin.common.util.SecureUtils; | ||||||
| import top.charles7c.cnadmin.common.util.TreeUtils; | import top.charles7c.cnadmin.common.util.TreeUtils; | ||||||
| @@ -71,6 +72,7 @@ import me.zhyd.oauth.model.AuthUser; | |||||||
| @RequiredArgsConstructor | @RequiredArgsConstructor | ||||||
| public class LoginServiceImpl implements LoginService { | public class LoginServiceImpl implements LoginService { | ||||||
|  |  | ||||||
|  |     private final ProjectProperties projectProperties; | ||||||
|     private final UserService userService; |     private final UserService userService; | ||||||
|     private final DeptService deptService; |     private final DeptService deptService; | ||||||
|     private final RoleService roleService; |     private final RoleService roleService; | ||||||
| @@ -79,7 +81,6 @@ public class LoginServiceImpl implements LoginService { | |||||||
|     private final UserRoleService userRoleService; |     private final UserRoleService userRoleService; | ||||||
|     private final UserSocialService userSocialService; |     private final UserSocialService userSocialService; | ||||||
|     private final MessageService messageService; |     private final MessageService messageService; | ||||||
|     private final ProjectProperties projectProperties; |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public String accountLogin(String username, String password) { |     public String accountLogin(String username, String password) { | ||||||
| @@ -137,7 +138,7 @@ public class LoginServiceImpl implements LoginService { | |||||||
|             userSocial.setUserId(userId); |             userSocial.setUserId(userId); | ||||||
|             userSocial.setSource(source); |             userSocial.setSource(source); | ||||||
|             userSocial.setOpenId(openId); |             userSocial.setOpenId(openId); | ||||||
|             this.sendMsg(user); |             this.sendSystemMsg(user); | ||||||
|         } else { |         } else { | ||||||
|             user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class); |             user = BeanUtil.toBean(userService.get(userSocial.getUserId()), UserDO.class); | ||||||
|         } |         } | ||||||
| @@ -214,20 +215,17 @@ public class LoginServiceImpl implements LoginService { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 发送消息 |      * 发送系统消息 | ||||||
|      *  |      *  | ||||||
|      * @param user |      * @param user | ||||||
|      *            用户信息 |      *            用户信息 | ||||||
|      */ |      */ | ||||||
|     private void sendMsg(UserDO user) { |     private void sendSystemMsg(UserDO user) { | ||||||
|         MessageRequest request = new MessageRequest(); |         MessageRequest request = new MessageRequest(); | ||||||
|         MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER; |         MessageTemplateEnum socialRegister = MessageTemplateEnum.SOCIAL_REGISTER; | ||||||
|         request.setTitle(socialRegister.getTitle()); |         request.setTitle(StrUtil.format(socialRegister.getTitle(), projectProperties.getName())); | ||||||
|         Map<String, Object> contentMap = MapUtil.newHashMap(2); |         request.setContent(StrUtil.format(socialRegister.getContent(), user.getNickname())); | ||||||
|         contentMap.put("nickname", user.getNickname()); |         request.setType(MessageTypeEnum.SYSTEM); | ||||||
|         contentMap.put("projectName", projectProperties.getName()); |  | ||||||
|         request.setContent(socialRegister.getContent(), contentMap); |  | ||||||
|         request.setType(SysConsts.SYSTEM_MESSAGE_TYPE); |  | ||||||
|         messageService.add(request, CollUtil.toList(user.getId())); |         messageService.add(request, CollUtil.toList(user.getId())); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ public enum MessageTemplateEnum { | |||||||
|     /** |     /** | ||||||
|      * 第三方登录 |      * 第三方登录 | ||||||
|      */ |      */ | ||||||
|     SOCIAL_REGISTER("欢迎注册 {projectName}", "尊敬的 {nickname},欢迎注册使用,请及时配置您的密码。"); |     SOCIAL_REGISTER("欢迎注册 {}", "尊敬的 {},欢迎注册使用,请及时配置您的密码。"); | ||||||
|  |  | ||||||
|     private final String title; |     private final String title; | ||||||
|     private final String content; |     private final String content; | ||||||
|   | |||||||
| @@ -16,8 +16,6 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.mapper; | package top.charles7c.cnadmin.system.mapper; | ||||||
|  |  | ||||||
| import java.util.List; |  | ||||||
|  |  | ||||||
| import org.apache.ibatis.annotations.Param; | import org.apache.ibatis.annotations.Param; | ||||||
|  |  | ||||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||||
| @@ -35,24 +33,16 @@ import top.charles7c.cnadmin.system.model.vo.MessageVO; | |||||||
|  * @since 2023/10/15 19:05 |  * @since 2023/10/15 19:05 | ||||||
|  */ |  */ | ||||||
| public interface MessageMapper extends BaseMapper<MessageDO> { | public interface MessageMapper extends BaseMapper<MessageDO> { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 分页查询列表 |      * 分页查询列表 | ||||||
|      * |      * | ||||||
|      * @param queryWrapper |  | ||||||
|      *            查询条件 |  | ||||||
|      * @param page |      * @param page | ||||||
|      *            分页查询条件 |      *            分页查询条件 | ||||||
|  |      * @param queryWrapper | ||||||
|  |      *            查询条件 | ||||||
|      * @return 分页信息 |      * @return 分页信息 | ||||||
|      */ |      */ | ||||||
|     IPage<MessageVO> selectVoPage(@Param("page") IPage<Object> page, |     IPage<MessageVO> selectVoPage(@Param("page") IPage<Object> page, | ||||||
|         @Param(Constants.WRAPPER) QueryWrapper<MessageDO> queryWrapper); |         @Param(Constants.WRAPPER) QueryWrapper<MessageDO> queryWrapper); | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 查询列表 |  | ||||||
|      * |  | ||||||
|      * @param queryWrapper |  | ||||||
|      *            查询条件 |  | ||||||
|      * @return 列表信息 |  | ||||||
|      */ |  | ||||||
|     List<MessageVO> selectVoList(@Param(Constants.WRAPPER) QueryWrapper<MessageDO> queryWrapper); |  | ||||||
| } | } | ||||||
| @@ -16,13 +16,27 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.mapper; | package top.charles7c.cnadmin.system.mapper; | ||||||
|  |  | ||||||
|  | import org.apache.ibatis.annotations.Param; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseMapper; | import top.charles7c.cnadmin.common.base.BaseMapper; | ||||||
| import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 消息和用户关联 Mapper |  * 消息和用户 Mapper | ||||||
|  * |  * | ||||||
|  * @author BULL_BCLS |  * @author BULL_BCLS | ||||||
|  * @since 2023/10/15 20:25 |  * @since 2023/10/15 20:25 | ||||||
|  */ |  */ | ||||||
| public interface MessageUserMapper extends BaseMapper<MessageUserDO> {} | public interface MessageUserMapper extends BaseMapper<MessageUserDO> { | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 根据用户 ID 和消息类型查询未读消息数量 | ||||||
|  |      *  | ||||||
|  |      * @param userId | ||||||
|  |      *            用户 ID | ||||||
|  |      * @param type | ||||||
|  |      *            消息类型 | ||||||
|  |      * @return 未读消息信息 | ||||||
|  |      */ | ||||||
|  |     Long selectUnreadCountByUserIdAndType(@Param("userId") Long userId, @Param("type") Integer type); | ||||||
|  | } | ||||||
| @@ -16,11 +16,17 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.model.entity; | package top.charles7c.cnadmin.system.model.entity; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
|  |  | ||||||
|  | import com.baomidou.mybatisplus.annotation.FieldFill; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableField; | ||||||
|  | import com.baomidou.mybatisplus.annotation.TableId; | ||||||
| import com.baomidou.mybatisplus.annotation.TableName; | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseDO; | import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 消息实体 |  * 消息实体 | ||||||
| @@ -30,17 +36,18 @@ import top.charles7c.cnadmin.common.base.BaseDO; | |||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| @TableName("sys_message") | @TableName("sys_message") | ||||||
| public class MessageDO extends BaseDO { | public class MessageDO implements Serializable { | ||||||
|  |  | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 消息ID |      * ID | ||||||
|      */ |      */ | ||||||
|  |     @TableId | ||||||
|     private Long id; |     private Long id; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 主题 |      * 标题 | ||||||
|      */ |      */ | ||||||
|     private String title; |     private String title; | ||||||
|  |  | ||||||
| @@ -50,7 +57,18 @@ public class MessageDO extends BaseDO { | |||||||
|     private String content; |     private String content; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 类型(取值于字典 message_type) |      * 类型(1:系统消息) | ||||||
|      */ |      */ | ||||||
|     private String type; |     private MessageTypeEnum type; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建人 | ||||||
|  |      */ | ||||||
|  |     private Long createUser; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建时间 | ||||||
|  |      */ | ||||||
|  |     @TableField(fill = FieldFill.INSERT) | ||||||
|  |     private LocalDateTime createTime; | ||||||
| } | } | ||||||
| @@ -16,14 +16,13 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.model.entity; | package top.charles7c.cnadmin.system.model.entity; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
|  |  | ||||||
| import com.baomidou.mybatisplus.annotation.TableName; | import com.baomidou.mybatisplus.annotation.TableName; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseDO; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 消息和用户关联实体 |  * 消息和用户关联实体 | ||||||
|  * |  * | ||||||
| @@ -32,7 +31,7 @@ import top.charles7c.cnadmin.common.base.BaseDO; | |||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| @TableName("sys_message_user") | @TableName("sys_message_user") | ||||||
| public class MessageUserDO extends BaseDO { | public class MessageUserDO implements Serializable { | ||||||
|  |  | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
| @@ -47,9 +46,9 @@ public class MessageUserDO extends BaseDO { | |||||||
|     private Long userId; |     private Long userId; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 读取状态 (0未读 1已读) |      * 是否已读 | ||||||
|      */ |      */ | ||||||
|     private Boolean readStatus; |     private Boolean isRead; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 读取时间 |      * 读取时间 | ||||||
|   | |||||||
| @@ -41,32 +41,32 @@ public class MessageQuery implements Serializable { | |||||||
|      * ID |      * ID | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "ID", example = "1") |     @Schema(description = "ID", example = "1") | ||||||
|     @Query(type = QueryTypeEnum.EQUAL) |     @Query | ||||||
|     private Long id; |     private Long id; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 类型(取值于字典 message_type) |      * 标题 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "类型(取值于字典 message_type)", example = "1") |     @Schema(description = "标题", example = "欢迎注册 xxx") | ||||||
|     @Query(type = QueryTypeEnum.EQUAL) |  | ||||||
|     private String type; |  | ||||||
|  |  | ||||||
|     /** |  | ||||||
|      * 主题 |  | ||||||
|      */ |  | ||||||
|     @Schema(description = "主题", example = "欢迎 xxx") |  | ||||||
|     @Query(type = QueryTypeEnum.INNER_LIKE) |     @Query(type = QueryTypeEnum.INNER_LIKE) | ||||||
|     private String title; |     private String title; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 用户ID |      * 类型 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "用户ID", example = "1") |     @Schema(description = "类型(1:系统消息)", example = "1") | ||||||
|     private Long uid; |     @Query | ||||||
|  |     private Integer type; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 是否已读 |      * 是否已读 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "是否已读", example = "true") |     @Schema(description = "是否已读", example = "true") | ||||||
|     private Boolean readStatus; |     private Boolean isRead; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 用户 ID | ||||||
|  |      */ | ||||||
|  |     @Schema(hidden = true) | ||||||
|  |     private Long userId; | ||||||
| } | } | ||||||
| @@ -16,9 +16,8 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.model.request; | package top.charles7c.cnadmin.system.model.request; | ||||||
|  |  | ||||||
| import java.util.Map; |  | ||||||
|  |  | ||||||
| import javax.validation.constraints.NotBlank; | import javax.validation.constraints.NotBlank; | ||||||
|  | import javax.validation.constraints.NotNull; | ||||||
|  |  | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
|  |  | ||||||
| @@ -26,9 +25,8 @@ import io.swagger.v3.oas.annotations.media.Schema; | |||||||
|  |  | ||||||
| import org.hibernate.validator.constraints.Length; | import org.hibernate.validator.constraints.Length; | ||||||
|  |  | ||||||
| import cn.hutool.core.util.StrUtil; |  | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseRequest; | import top.charles7c.cnadmin.common.base.BaseRequest; | ||||||
|  | import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 创建消息信息 |  * 创建消息信息 | ||||||
| @@ -36,37 +34,32 @@ import top.charles7c.cnadmin.common.base.BaseRequest; | |||||||
|  * @author BULL_BCLS |  * @author BULL_BCLS | ||||||
|  * @since 2023/10/15 19:05 |  * @since 2023/10/15 19:05 | ||||||
|  */ |  */ | ||||||
| @Schema(description = "创建消息信息") |  | ||||||
| @Data | @Data | ||||||
|  | @Schema(description = "创建消息信息") | ||||||
| public class MessageRequest extends BaseRequest { | public class MessageRequest extends BaseRequest { | ||||||
|  |  | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 主题 |      * 标题 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "主题", example = "欢迎 xxx") |     @Schema(description = "标题", example = "欢迎注册 xxx") | ||||||
|     @NotBlank(message = "主题不能为空") |     @NotBlank(message = "标题不能为空") | ||||||
|     @Length(max = 50, message = "主题长度不能超过 {max} 个字符") |     @Length(max = 50, message = "标题长度不能超过 {max} 个字符") | ||||||
|     private String title; |     private String title; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 内容 |      * 内容 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "内容", example = "欢迎 xxx 来到 ContiNew Admin") |     @Schema(description = "内容", example = "尊敬的 xx,欢迎注册使用,请及时配置您的密码。") | ||||||
|     @NotBlank(message = "内容不能为空") |     @NotBlank(message = "内容不能为空") | ||||||
|     @Length(max = 255, message = "内容长度不能超过 {max} 个字符") |     @Length(max = 255, message = "内容长度不能超过 {max} 个字符") | ||||||
|     private String content; |     private String content; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 类型(取值于字典 message_type) |      * 类型 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "类型(取值于字典 message_type)", example = "1") |     @Schema(description = "类型(1:系统消息)", example = "1") | ||||||
|     @NotBlank(message = "类型不能为空") |     @NotNull(message = "类型非法") | ||||||
|     @Length(max = 30, message = "类型长度不能超过 {max} 个字符") |     private MessageTypeEnum type; | ||||||
|     private String type; |  | ||||||
|  |  | ||||||
|     public void setContent(String content, Map<String, Object> contentMap) { |  | ||||||
|         this.content = StrUtil.format(content, contentMap); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
| @@ -16,47 +16,35 @@ | |||||||
| 
 | 
 | ||||||
| package top.charles7c.cnadmin.system.model.vo; | package top.charles7c.cnadmin.system.model.vo; | ||||||
| 
 | 
 | ||||||
| import java.time.LocalDateTime; | import java.io.Serializable; | ||||||
| 
 | 
 | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
| 
 | 
 | ||||||
| import io.swagger.v3.oas.annotations.media.Schema; | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
| 
 | 
 | ||||||
| import top.charles7c.cnadmin.common.base.BaseVO; | import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * 消息和用户关联信息 |  * 各类型未读消息信息 | ||||||
|  * |  * | ||||||
|  * @author BULL_BCLS |  * @author Charles7c | ||||||
|  * @since 2023/10/15 20:25 |  * @since 2023/11/2 23:00 | ||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| @Schema(description = "消息和用户关联信息") | @Schema(description = "各类型未读消息信息") | ||||||
| public class MessageUserVO extends BaseVO { | public class MessageTypeUnreadVO implements Serializable { | ||||||
| 
 | 
 | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 消息ID |      * 类型 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "消息ID", example = "1") |     @Schema(description = "类型(1:系统消息)", example = "1") | ||||||
|     private Long messageId; |     private MessageTypeEnum type; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * 用户ID |      * 数量 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "用户ID", example = "1") |     @Schema(description = "数量", example = "10") | ||||||
|     private Long userId; |     private Long count; | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 是否已读 |  | ||||||
|      */ |  | ||||||
|     @Schema(description = "是否已读", example = "true") |  | ||||||
|     private Boolean readStatus; |  | ||||||
| 
 |  | ||||||
|     /** |  | ||||||
|      * 读取时间 |  | ||||||
|      */ |  | ||||||
|     @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") |  | ||||||
|     private LocalDateTime readTime; |  | ||||||
| } | } | ||||||
| @@ -0,0 +1,52 @@ | |||||||
|  | /* | ||||||
|  |  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||||
|  |  * | ||||||
|  |  * Licensed under the Apache License, Version 2.0 (the "License"); | ||||||
|  |  * you may not use this file except in compliance with the License. | ||||||
|  |  * You may obtain a copy of the License at | ||||||
|  |  * | ||||||
|  |  *     http://www.apache.org/licenses/LICENSE-2.0 | ||||||
|  |  * | ||||||
|  |  * Unless required by applicable law or agreed to in writing, software | ||||||
|  |  * distributed under the License is distributed on an "AS IS" BASIS, | ||||||
|  |  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||||
|  |  * See the License for the specific language governing permissions and | ||||||
|  |  * limitations under the License. | ||||||
|  |  */ | ||||||
|  |  | ||||||
|  | package top.charles7c.cnadmin.system.model.vo; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | import lombok.Data; | ||||||
|  |  | ||||||
|  | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
|  | import com.fasterxml.jackson.annotation.JsonInclude; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * 未读消息信息 | ||||||
|  |  * | ||||||
|  |  * @author Charles7c | ||||||
|  |  * @since 2023/11/2 23:00 | ||||||
|  |  */ | ||||||
|  | @Data | ||||||
|  | @Schema(description = "未读消息信息") | ||||||
|  | @JsonInclude(JsonInclude.Include.NON_EMPTY) | ||||||
|  | public class MessageUnreadVO implements Serializable { | ||||||
|  |  | ||||||
|  |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 未读消息数量 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "未读消息数量", example = "20") | ||||||
|  |     private Long total; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 各类型未读消息数量 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "各类型未读消息数量") | ||||||
|  |     private List<MessageTypeUnreadVO> details; | ||||||
|  | } | ||||||
| @@ -16,13 +16,16 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.model.vo; | package top.charles7c.cnadmin.system.model.vo; | ||||||
|  |  | ||||||
|  | import java.io.Serializable; | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
|  |  | ||||||
| import lombok.Data; | import lombok.Data; | ||||||
|  |  | ||||||
| import io.swagger.v3.oas.annotations.media.Schema; | import io.swagger.v3.oas.annotations.media.Schema; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseVO; | import com.fasterxml.jackson.annotation.JsonIgnore; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 消息信息 |  * 消息信息 | ||||||
| @@ -32,43 +35,61 @@ import top.charles7c.cnadmin.common.base.BaseVO; | |||||||
|  */ |  */ | ||||||
| @Data | @Data | ||||||
| @Schema(description = "消息信息") | @Schema(description = "消息信息") | ||||||
| public class MessageVO extends BaseVO { | public class MessageVO implements Serializable { | ||||||
|  |  | ||||||
|     private static final long serialVersionUID = 1L; |     private static final long serialVersionUID = 1L; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 消息ID |      * ID | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "消息ID", example = "1") |     @Schema(description = "ID", example = "1") | ||||||
|     private Long id; |     private Long id; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 主题 |      * 标题 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "主题", example = "欢迎 xxx") |     @Schema(description = "标题", example = "欢迎注册 xxx") | ||||||
|     private String title; |     private String title; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 内容 |      * 内容 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "内容", example = "欢迎 xxx") |     @Schema(description = "内容", example = "尊敬的 xx,欢迎注册使用,请及时配置您的密码。") | ||||||
|     private String content; |     private String content; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 类型(取值于字典 message_type) |      * 类型 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "类型(取值于字典 message_type)", example = "1") |     @Schema(description = "类型(1:系统消息)", example = "1") | ||||||
|     private String type; |     private MessageTypeEnum type; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 是否已读 |      * 是否已读 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "是否已读", example = "true") |     @Schema(description = "是否已读", example = "true") | ||||||
|     private Boolean readStatus; |     private Boolean isRead; | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 读取时间 |      * 读取时间 | ||||||
|      */ |      */ | ||||||
|     @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") |     @Schema(description = "读取时间", example = "2023-08-08 23:59:59", type = "string") | ||||||
|     private LocalDateTime readTime; |     private LocalDateTime readTime; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建人 | ||||||
|  |      */ | ||||||
|  |     @JsonIgnore | ||||||
|  |     private Long createUser; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建人 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "创建人", example = "超级管理员") | ||||||
|  |     private String createUserString; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 创建时间 | ||||||
|  |      */ | ||||||
|  |     @Schema(description = "创建时间", example = "2023-08-08 08:08:08", type = "string") | ||||||
|  |     private LocalDateTime createTime; | ||||||
| } | } | ||||||
| @@ -18,7 +18,8 @@ package top.charles7c.cnadmin.system.service; | |||||||
|  |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseService; | import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||||
|  | import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||||
| import top.charles7c.cnadmin.system.model.query.MessageQuery; | import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||||
| import top.charles7c.cnadmin.system.model.request.MessageRequest; | import top.charles7c.cnadmin.system.model.request.MessageRequest; | ||||||
| import top.charles7c.cnadmin.system.model.vo.MessageVO; | import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||||
| @@ -29,15 +30,34 @@ import top.charles7c.cnadmin.system.model.vo.MessageVO; | |||||||
|  * @author BULL_BCLS |  * @author BULL_BCLS | ||||||
|  * @since 2023/10/15 19:05 |  * @since 2023/10/15 19:05 | ||||||
|  */ |  */ | ||||||
| public interface MessageService extends BaseService<MessageVO, MessageVO, MessageQuery, MessageRequest> { | public interface MessageService { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 发送消息 |      * 分页查询列表 | ||||||
|  |      * | ||||||
|  |      * @param query | ||||||
|  |      *            查询条件 | ||||||
|  |      * @param pageQuery | ||||||
|  |      *            分页查询条件 | ||||||
|  |      * @return 分页列表信息 | ||||||
|  |      */ | ||||||
|  |     PageDataVO<MessageVO> page(MessageQuery query, PageQuery pageQuery); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 新增 | ||||||
|      * |      * | ||||||
|      * @param request |      * @param request | ||||||
|      *            消息 |      *            消息 | ||||||
|      * @param userIdList |      * @param userIdList | ||||||
|      *            接收人 |      *            接收人列表 | ||||||
|      */ |      */ | ||||||
|     void add(MessageRequest request, List<Long> userIdList); |     void add(MessageRequest request, List<Long> userIdList); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 删除 | ||||||
|  |      * | ||||||
|  |      * @param ids | ||||||
|  |      *            ID 列表 | ||||||
|  |      */ | ||||||
|  |     void delete(List<Long> ids); | ||||||
| } | } | ||||||
| @@ -18,6 +18,8 @@ package top.charles7c.cnadmin.system.service; | |||||||
|  |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.system.model.vo.MessageUnreadVO; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 消息和用户关联业务接口 |  * 消息和用户关联业务接口 | ||||||
|  * |  * | ||||||
| @@ -27,12 +29,23 @@ import java.util.List; | |||||||
| public interface MessageUserService { | public interface MessageUserService { | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 发送消息 |      * 根据用户 ID 查询未读消息数量 | ||||||
|  |      *  | ||||||
|  |      * @param userId | ||||||
|  |      *            用户 ID | ||||||
|  |      * @param isDetail | ||||||
|  |      *            是否查询详情 | ||||||
|  |      * @return 未读消息信息 | ||||||
|  |      */ | ||||||
|  |     MessageUnreadVO countUnreadMessageByUserId(Long userId, Boolean isDetail); | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 新增 | ||||||
|      * |      * | ||||||
|      * @param messageId |      * @param messageId | ||||||
|      *            消息 ID |      *            消息 ID | ||||||
|      * @param userIdList |      * @param userIdList | ||||||
|      *            接收人 |      *            用户 ID 列表 | ||||||
|      */ |      */ | ||||||
|     void add(Long messageId, List<Long> userIdList); |     void add(Long messageId, List<Long> userIdList); | ||||||
|  |  | ||||||
| @@ -45,10 +58,10 @@ public interface MessageUserService { | |||||||
|     void readMessage(List<Long> ids); |     void readMessage(List<Long> ids); | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 删除消息 |      * 根据消息 ID 删除 | ||||||
|      * |      * | ||||||
|      * @param ids |      * @param messageIds | ||||||
|      *            消息ID |      *            消息 ID 列表 | ||||||
|      */ |      */ | ||||||
|     void delete(List<Long> ids); |     void deleteByMessageIds(List<Long> messageIds); | ||||||
| } | } | ||||||
| @@ -16,7 +16,6 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.system.service.impl; | package top.charles7c.cnadmin.system.service.impl; | ||||||
|  |  | ||||||
| import java.util.Collections; |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| @@ -27,13 +26,14 @@ import org.springframework.transaction.annotation.Transactional; | |||||||
| import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||||||
| import com.baomidou.mybatisplus.core.metadata.IPage; | import com.baomidou.mybatisplus.core.metadata.IPage; | ||||||
|  |  | ||||||
|  | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
|  | import cn.hutool.extra.spring.SpringUtil; | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.base.BaseServiceImpl; |  | ||||||
| import top.charles7c.cnadmin.common.model.query.PageQuery; | import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||||
| import top.charles7c.cnadmin.common.model.query.SortQuery; |  | ||||||
| import top.charles7c.cnadmin.common.model.vo.PageDataVO; | import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||||
| import top.charles7c.cnadmin.common.util.helper.LoginHelper; | import top.charles7c.cnadmin.common.service.CommonUserService; | ||||||
|  | import top.charles7c.cnadmin.common.util.ExceptionUtils; | ||||||
| import top.charles7c.cnadmin.common.util.helper.QueryHelper; | import top.charles7c.cnadmin.common.util.helper.QueryHelper; | ||||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||||
| import top.charles7c.cnadmin.system.mapper.MessageMapper; | import top.charles7c.cnadmin.system.mapper.MessageMapper; | ||||||
| @@ -52,57 +52,48 @@ import top.charles7c.cnadmin.system.service.MessageUserService; | |||||||
|  */ |  */ | ||||||
| @Service | @Service | ||||||
| @RequiredArgsConstructor | @RequiredArgsConstructor | ||||||
| public class MessageServiceImpl | public class MessageServiceImpl implements MessageService { | ||||||
|     extends BaseServiceImpl<MessageMapper, MessageDO, MessageVO, MessageVO, MessageQuery, MessageRequest> |  | ||||||
|     implements MessageService { |  | ||||||
|  |  | ||||||
|  |     private final MessageMapper baseMapper; | ||||||
|     private final MessageUserService messageUserService; |     private final MessageUserService messageUserService; | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public PageDataVO<MessageVO> page(MessageQuery query, PageQuery pageQuery) { |     public PageDataVO<MessageVO> page(MessageQuery query, PageQuery pageQuery) { | ||||||
|         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query); |         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query); | ||||||
|         queryWrapper.apply(null != query.getUid(), "msgUser.user_id={0}", query.getUid()) |         queryWrapper.apply(null != query.getUserId(), "t2.user_id={0}", query.getUserId()) | ||||||
|             .apply(null != query.getReadStatus(), "msgUser.read_status={0}", query.getReadStatus()); |             .apply(null != query.getIsRead(), "t2.is_read={0}", query.getIsRead()); | ||||||
|         IPage<MessageVO> page = baseMapper.selectVoPage(pageQuery.toPage(), queryWrapper); |         IPage<MessageVO> page = baseMapper.selectVoPage(pageQuery.toPage(), queryWrapper); | ||||||
|         page.getRecords().forEach(this::fill); |         page.getRecords().forEach(this::fill); | ||||||
|         return PageDataVO.build(page); |         return PageDataVO.build(page); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public List<MessageVO> list(MessageQuery query, SortQuery sortQuery) { |  | ||||||
|         QueryWrapper<MessageDO> queryWrapper = QueryHelper.build(query); |  | ||||||
|         queryWrapper.apply("msgUser.user_id={0}", LoginHelper.getUserId()).apply(null != query.getReadStatus(), |  | ||||||
|             "msgUser.read_status={0}", query.getReadStatus()); |  | ||||||
|         // 设置排序 |  | ||||||
|         this.sort(queryWrapper, sortQuery); |  | ||||||
|         return baseMapper.selectVoList(queryWrapper); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |  | ||||||
|     public MessageVO get(Long id) { |  | ||||||
|         MessageQuery messageQuery = new MessageQuery(); |  | ||||||
|         messageQuery.setId(id); |  | ||||||
|         PageDataVO<MessageVO> page = this.page(messageQuery, new PageQuery()); |  | ||||||
|         List<MessageVO> messageVOList = page.getList(); |  | ||||||
|         if (CollUtil.isEmpty(messageVOList)) { |  | ||||||
|             return new MessageVO(); |  | ||||||
|         } |  | ||||||
|         MessageVO messageVO = messageVOList.get(0); |  | ||||||
|         messageUserService.readMessage(Collections.singletonList(messageVO.getId())); |  | ||||||
|         return messageVO; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void add(MessageRequest request, List<Long> userIdList) { |     public void add(MessageRequest request, List<Long> userIdList) { | ||||||
|         CheckUtils.throwIf(() -> CollUtil.isEmpty(userIdList), "消息接收人不能为空"); |         CheckUtils.throwIf(() -> CollUtil.isEmpty(userIdList), "消息接收人不能为空"); | ||||||
|         Long messageId = super.add(request); |         MessageDO message = BeanUtil.copyProperties(request, MessageDO.class); | ||||||
|         messageUserService.add(messageId, userIdList); |         baseMapper.insert(message); | ||||||
|  |         messageUserService.add(message.getId(), userIdList); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     @Transactional(rollbackFor = Exception.class) |     @Transactional(rollbackFor = Exception.class) | ||||||
|     public void delete(List<Long> ids) { |     public void delete(List<Long> ids) { | ||||||
|         super.delete(ids); |         baseMapper.deleteBatchIds(ids); | ||||||
|         messageUserService.delete(ids); |         messageUserService.deleteByMessageIds(ids); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * 填充数据 | ||||||
|  |      * | ||||||
|  |      * @param message | ||||||
|  |      *            待填充信息 | ||||||
|  |      */ | ||||||
|  |     private void fill(MessageVO message) { | ||||||
|  |         Long createUser = message.getCreateUser(); | ||||||
|  |         if (null == createUser) { | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         CommonUserService userService = SpringUtil.getBean(CommonUserService.class); | ||||||
|  |         message.setCreateUserString(ExceptionUtils.exToNull(() -> userService.getNicknameById(createUser))); | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -17,6 +17,7 @@ | |||||||
| package top.charles7c.cnadmin.system.service.impl; | package top.charles7c.cnadmin.system.service.impl; | ||||||
|  |  | ||||||
| import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||||
|  | import java.util.ArrayList; | ||||||
| import java.util.List; | import java.util.List; | ||||||
| import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||||
|  |  | ||||||
| @@ -24,13 +25,14 @@ import lombok.RequiredArgsConstructor; | |||||||
|  |  | ||||||
| import org.springframework.stereotype.Service; | import org.springframework.stereotype.Service; | ||||||
|  |  | ||||||
| import com.baomidou.mybatisplus.core.toolkit.Wrappers; |  | ||||||
|  |  | ||||||
| import cn.hutool.core.collection.CollUtil; | import cn.hutool.core.collection.CollUtil; | ||||||
|  |  | ||||||
|  | import top.charles7c.cnadmin.common.enums.MessageTypeEnum; | ||||||
| import top.charles7c.cnadmin.common.util.validate.CheckUtils; | import top.charles7c.cnadmin.common.util.validate.CheckUtils; | ||||||
| import top.charles7c.cnadmin.system.mapper.MessageUserMapper; | import top.charles7c.cnadmin.system.mapper.MessageUserMapper; | ||||||
| import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | import top.charles7c.cnadmin.system.model.entity.MessageUserDO; | ||||||
|  | import top.charles7c.cnadmin.system.model.vo.MessageTypeUnreadVO; | ||||||
|  | import top.charles7c.cnadmin.system.model.vo.MessageUnreadVO; | ||||||
| import top.charles7c.cnadmin.system.service.MessageUserService; | import top.charles7c.cnadmin.system.service.MessageUserService; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -43,32 +45,52 @@ import top.charles7c.cnadmin.system.service.MessageUserService; | |||||||
| @RequiredArgsConstructor | @RequiredArgsConstructor | ||||||
| public class MessageUserServiceImpl implements MessageUserService { | public class MessageUserServiceImpl implements MessageUserService { | ||||||
|  |  | ||||||
|     private final MessageUserMapper messageUserMapper; |     private final MessageUserMapper baseMapper; | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public MessageUnreadVO countUnreadMessageByUserId(Long userId, Boolean isDetail) { | ||||||
|  |         MessageUnreadVO result = new MessageUnreadVO(); | ||||||
|  |         Long total = 0L; | ||||||
|  |         if (Boolean.TRUE.equals(isDetail)) { | ||||||
|  |             List<MessageTypeUnreadVO> detailList = new ArrayList<>(); | ||||||
|  |             for (MessageTypeEnum messageType : MessageTypeEnum.values()) { | ||||||
|  |                 MessageTypeUnreadVO vo = new MessageTypeUnreadVO(); | ||||||
|  |                 vo.setType(messageType); | ||||||
|  |                 Long count = baseMapper.selectUnreadCountByUserIdAndType(userId, messageType.getValue()); | ||||||
|  |                 vo.setCount(count); | ||||||
|  |                 detailList.add(vo); | ||||||
|  |                 total += count; | ||||||
|  |             } | ||||||
|  |             result.setDetails(detailList); | ||||||
|  |         } else { | ||||||
|  |             total = baseMapper.selectUnreadCountByUserIdAndType(userId, null); | ||||||
|  |         } | ||||||
|  |         result.setTotal(total); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void add(Long messageId, List<Long> userIdList) { |     public void add(Long messageId, List<Long> userIdList) { | ||||||
|         CheckUtils.throwIf(() -> CollUtil.isEmpty(userIdList), "消息接收人不能为空"); |         CheckUtils.throwIfEmpty(userIdList, "消息接收人不能为空"); | ||||||
|         List<MessageUserDO> messageUserDOList = userIdList.stream().map(userId -> { |         List<MessageUserDO> messageUserList = userIdList.stream().map(userId -> { | ||||||
|             MessageUserDO messageUserDO = new MessageUserDO(); |             MessageUserDO messageUser = new MessageUserDO(); | ||||||
|             messageUserDO.setUserId(userId); |             messageUser.setUserId(userId); | ||||||
|             messageUserDO.setMessageId(messageId); |             messageUser.setMessageId(messageId); | ||||||
|             messageUserDO.setReadStatus(false); |             messageUser.setIsRead(false); | ||||||
|             return messageUserDO; |             return messageUser; | ||||||
|         }).collect(Collectors.toList()); |         }).collect(Collectors.toList()); | ||||||
|         messageUserMapper.insertBatch(messageUserDOList); |         baseMapper.insertBatch(messageUserList); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void readMessage(List<Long> ids) { |     public void readMessage(List<Long> ids) { | ||||||
|         messageUserMapper.lambdaUpdate().set(MessageUserDO::getReadStatus, true) |         baseMapper.lambdaUpdate().set(MessageUserDO::getIsRead, true) | ||||||
|             .set(MessageUserDO::getReadTime, LocalDateTime.now()).eq(MessageUserDO::getReadStatus, false) |             .set(MessageUserDO::getReadTime, LocalDateTime.now()).eq(MessageUserDO::getIsRead, false) | ||||||
|             .in(CollUtil.isNotEmpty(ids), MessageUserDO::getMessageId, ids).update(); |             .in(CollUtil.isNotEmpty(ids), MessageUserDO::getMessageId, ids).update(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void delete(List<Long> ids) { |     public void deleteByMessageIds(List<Long> messageIds) { | ||||||
|         if (CollUtil.isNotEmpty(ids)) { |         baseMapper.lambdaUpdate().in(MessageUserDO::getMessageId, messageIds).remove(); | ||||||
|             messageUserMapper.delete(Wrappers.<MessageUserDO>lambdaQuery().in(MessageUserDO::getMessageId, ids)); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -2,31 +2,13 @@ | |||||||
|         "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > |         "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | ||||||
| <mapper namespace="top.charles7c.cnadmin.system.mapper.MessageMapper"> | <mapper namespace="top.charles7c.cnadmin.system.mapper.MessageMapper"> | ||||||
|     <select id="selectVoPage" resultType="top.charles7c.cnadmin.system.model.vo.MessageVO"> |     <select id="selectVoPage" resultType="top.charles7c.cnadmin.system.model.vo.MessageVO"> | ||||||
|         SELECT msg.id, |         SELECT | ||||||
|                msg.type, |             t1.*, | ||||||
|                msg.title, |             t2.`user_id`, | ||||||
|                msg.content, |             t2.`is_read`, | ||||||
|                msg.create_user, |             t2.`read_time` | ||||||
|                msg.create_time, |         FROM `sys_message` AS t1 | ||||||
|                msgUser.read_status, |             LEFT JOIN `sys_message_user` AS t2 ON t2.`message_id` = t1.`id` | ||||||
|                msgUser.read_time, |  | ||||||
|                msgUser.user_id |  | ||||||
|         FROM `sys_message` msg |  | ||||||
|                  LEFT JOIN sys_message_user msgUser ON msg.id = msgUser.message_id |  | ||||||
|         ${ew.getCustomSqlSegment} |  | ||||||
|     </select> |  | ||||||
|     <select id="selectVoList" resultType="top.charles7c.cnadmin.system.model.vo.MessageVO"> |  | ||||||
|         SELECT msg.id, |  | ||||||
|                msg.type, |  | ||||||
|                msg.title, |  | ||||||
|                msg.content, |  | ||||||
|                msg.create_user, |  | ||||||
|                msg.create_time, |  | ||||||
|                msgUser.read_status, |  | ||||||
|                msgUser.read_time, |  | ||||||
|                msgUser.user_id |  | ||||||
|         FROM `sys_message` msg |  | ||||||
|                  LEFT JOIN sys_message_user msgUser ON msg.id = msgUser.message_id |  | ||||||
|         ${ew.getCustomSqlSegment} |         ${ew.getCustomSqlSegment} | ||||||
|     </select> |     </select> | ||||||
| </mapper> | </mapper> | ||||||
|   | |||||||
| @@ -0,0 +1,14 @@ | |||||||
|  | <?xml version="1.0" encoding="UTF-8" ?> | ||||||
|  | <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > | ||||||
|  | <mapper namespace="top.charles7c.cnadmin.system.mapper.MessageUserMapper"> | ||||||
|  |     <select id="selectUnreadCountByUserIdAndType" resultType="Long"> | ||||||
|  |         SELECT | ||||||
|  |             COUNT(t1.`message_id`) | ||||||
|  |         FROM `sys_message_user` AS t1 | ||||||
|  |             LEFT JOIN `sys_message` AS t2 ON t2.`id` = t1.`message_id` | ||||||
|  |         WHERE t1.`user_id` = #{userId} AND t1.`is_read` = 0 | ||||||
|  |         <if test="type != null"> | ||||||
|  |             AND t2.`type` = #{type} | ||||||
|  |         </if> | ||||||
|  |     </select> | ||||||
|  | </mapper> | ||||||
| @@ -3,45 +3,33 @@ import qs from 'query-string'; | |||||||
|  |  | ||||||
| const BASE_URL = '/system/message'; | const BASE_URL = '/system/message'; | ||||||
|  |  | ||||||
| export interface MessageRecord { | export interface DataRecord { | ||||||
|   id: number; |   id: number; | ||||||
|   title: string; |   title: string; | ||||||
|   content: string; |   content: string; | ||||||
|   type: string; |   type: number; | ||||||
|   createUserString: string; |   createUserString?: string; | ||||||
|   createTime: string; |   createTime: string; | ||||||
|   subTitle: string; |   isRead: boolean; | ||||||
|   readStatus: boolean; |  | ||||||
|   readTime: string; |   readTime: string; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface ChatRecord { |  | ||||||
|   id: number; |  | ||||||
|   username: string; |  | ||||||
|   content: string; |  | ||||||
|   time: string; |  | ||||||
|   isCollect: boolean; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export interface ListParam { | export interface ListParam { | ||||||
|   title?: string; |   title?: string; | ||||||
|   readStatus?: 0 | 1; |   type?: number; | ||||||
|   type?: string; |   isRead?: boolean; | ||||||
|   page?: number; |   page?: number; | ||||||
|   size?: number; |   size?: number; | ||||||
|   uid?: number; |  | ||||||
|   sort?: Array<string>; |   sort?: Array<string>; | ||||||
| } | } | ||||||
|  |  | ||||||
| export interface PageRes { | export interface ListRes { | ||||||
|   list: MessageRecord[]; |   list: DataRecord[]; | ||||||
|   total: number; |   total: number; | ||||||
| } | } | ||||||
|  |  | ||||||
| export type MessageListType = MessageRecord[]; | export function list(params: ListParam) { | ||||||
|  |   return axios.get<ListRes>(`${BASE_URL}`, { | ||||||
| export function page(params?: ListParam) { |  | ||||||
|   return axios.get<PageRes>(`${BASE_URL}`, { |  | ||||||
|     params, |     params, | ||||||
|     paramsSerializer: (obj) => { |     paramsSerializer: (obj) => { | ||||||
|       return qs.stringify(obj); |       return qs.stringify(obj); | ||||||
| @@ -49,27 +37,24 @@ export function page(params?: ListParam) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function list(params?: ListParam) { |  | ||||||
|   return axios.get<MessageListType>(`${BASE_URL}/list`, { |  | ||||||
|     params, |  | ||||||
|     paramsSerializer: (obj) => { |  | ||||||
|       return qs.stringify(obj); |  | ||||||
|     }, |  | ||||||
|   }); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function get(id: number) { |  | ||||||
|   return axios.get<MessageRecord>(`${BASE_URL}/${id}`); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export function del(ids: number | Array<number>) { | export function del(ids: number | Array<number>) { | ||||||
|   return axios.delete(`${BASE_URL}/${ids}`); |   return axios.delete(`${BASE_URL}/${ids}`); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function read(data: Array<number>) { | export function read(ids: Array<number>) { | ||||||
|   return axios.patch<MessageListType>(`${BASE_URL}/read?ids=${data}`); |   return axios.patch(`${BASE_URL}/read?ids=${ids}`); | ||||||
| } | } | ||||||
|  |  | ||||||
| export function queryChatList() { | export interface MessageTypeUnreadRes { | ||||||
|   return axios.get<ChatRecord[]>('/api/chat/list'); |   type: number; | ||||||
|  |   count: number; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export interface MessageUnreadRes { | ||||||
|  |   total: number; | ||||||
|  |   details: MessageTypeUnreadRes[]; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export function countUnread(detail: boolean) { | ||||||
|  |   return axios.get<MessageUnreadRes>(`${BASE_URL}/unread?detail=${detail}`); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,56 +1,68 @@ | |||||||
| <template> | <template> | ||||||
|   <a-spin style="display: block" :loading="loading"> |   <a-spin style="display: block" :loading="loading"> | ||||||
|     <a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide> |     <a-tabs v-model:activeKey="messageType" type="rounded" destroy-on-hide> | ||||||
|       <a-tab-pane v-for="item in message_type" :key="item.value"> |       <a-tab-pane :key="1"> | ||||||
|         <template #title> |         <template #title> | ||||||
|           <span> {{ item.label }}{{ formatUnreadLength(item.value) }} </span> |           <span> | ||||||
|  |             {{ $t('messageBox.tab.title.message.system') | ||||||
|  |             }}{{ formatUnreadCount(messageType) }} | ||||||
|  |           </span> | ||||||
|         </template> |         </template> | ||||||
|         <a-result v-if="!renderList.length" status="404"> |         <a-result v-if="!messageList.length" status="404"> | ||||||
|           <template #subtitle> {{ $t('messageBox.noContent') }} </template> |           <template #subtitle> {{ $t('messageBox.noContent') }} </template> | ||||||
|         </a-result> |         </a-result> | ||||||
|         <List |         <List :render-list="messageList" @item-click="handleItemClick" /> | ||||||
|           :render-list="renderList" |  | ||||||
|           :unread-count="unreadCount" |  | ||||||
|           @item-click="handleItemClick" |  | ||||||
|         /> |  | ||||||
|       </a-tab-pane> |       </a-tab-pane> | ||||||
|     </a-tabs> |     </a-tabs> | ||||||
|   </a-spin> |   </a-spin> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { computed, getCurrentInstance, reactive, ref, toRefs } from 'vue'; |   import { reactive, ref, toRefs } from 'vue'; | ||||||
|   import { |   import { | ||||||
|     MessageListType, |     DataRecord, | ||||||
|     MessageRecord, |     MessageUnreadRes, | ||||||
|     list, |     list, | ||||||
|     read, |     read, | ||||||
|  |     countUnread, | ||||||
|  |     ListParam, | ||||||
|   } from '@/api/system/message'; |   } from '@/api/system/message'; | ||||||
|   import useLoading from '@/hooks/loading'; |   import useLoading from '@/hooks/loading'; | ||||||
|   import List from './list.vue'; |   import List from './list.vue'; | ||||||
|  |  | ||||||
|   const { proxy } = getCurrentInstance() as any; |   const { loading, setLoading } = useLoading(); | ||||||
|   const { message_type } = proxy.useDict('message_type'); |   const messageType = ref(1); | ||||||
|   const { loading, setLoading } = useLoading(true); |   const unreadCount = ref<MessageUnreadRes>(); | ||||||
|   const messageType = ref('1'); |   const messageList = ref<DataRecord[]>([]); | ||||||
|  |   const data = reactive({ | ||||||
|   const messageData = reactive<{ |     // 查询参数 | ||||||
|     renderList: MessageRecord[]; |     queryParams: { | ||||||
|     messageList: MessageRecord[]; |       type: messageType.value, | ||||||
|   }>({ |       isRead: false, | ||||||
|     renderList: [], |       page: 1, | ||||||
|     messageList: [], |       size: 3, | ||||||
|  |       sort: ['createTime,desc'], | ||||||
|  |     }, | ||||||
|   }); |   }); | ||||||
|   toRefs(messageData); |   const { queryParams } = toRefs(data); | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 查询列表 |    * 查询未读消息数量 | ||||||
|    */ |    */ | ||||||
|   async function fetchSourceData() { |   async function getUnreadCount() { | ||||||
|  |     const res = await countUnread(true); | ||||||
|  |     unreadCount.value = res.data; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * 查询未读消息列表 | ||||||
|  |    */ | ||||||
|  |   async function getList(params: ListParam = { ...queryParams.value }) { | ||||||
|  |     await getUnreadCount(); | ||||||
|     setLoading(true); |     setLoading(true); | ||||||
|     try { |     try { | ||||||
|       list({ sort: ['createTime,desc'] }).then((res) => { |       await list(params).then((res) => { | ||||||
|         messageData.messageList = res.data; |         messageList.value = res.data.list; | ||||||
|       }); |       }); | ||||||
|     } finally { |     } finally { | ||||||
|       setLoading(false); |       setLoading(false); | ||||||
| @@ -60,49 +72,25 @@ | |||||||
|   /** |   /** | ||||||
|    * 将消息设置为已读 |    * 将消息设置为已读 | ||||||
|    * |    * | ||||||
|    * @param data 消息列表 |    * @param items 消息列表 | ||||||
|    */ |    */ | ||||||
|   async function readMessage(data: MessageListType) { |   async function readMessage(items: DataRecord[]) { | ||||||
|     const ids = data.map((item) => item.id); |     const ids = items.map((item) => item.id); | ||||||
|     await read(ids); |     await read(ids); | ||||||
|     await fetchSourceData(); |     await getList(); | ||||||
|  |     await getUnreadCount(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 每个消息类型下的消息列表 |  | ||||||
|    */ |  | ||||||
|   const renderList = computed(() => { |  | ||||||
|     return messageData.messageList |  | ||||||
|       .filter((item) => item.type === messageType.value && !item.readStatus) |  | ||||||
|       .splice(0, 3); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 未读消息数量 |  | ||||||
|    */ |  | ||||||
|   const unreadCount = computed(() => { |  | ||||||
|     return renderList.value.filter((item) => !item.readStatus).length; |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   /** |  | ||||||
|    * 未读消息列表 |  | ||||||
|    * |  | ||||||
|    * @param type 消息类型 |  | ||||||
|    */ |  | ||||||
|   const getUnreadList = (type: string) => { |  | ||||||
|     return messageData.messageList.filter( |  | ||||||
|       (item) => item.type === type && !item.readStatus |  | ||||||
|     ); |  | ||||||
|   }; |  | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 每个类型的未读消息数量 |    * 每个类型的未读消息数量 | ||||||
|    * |    * | ||||||
|    * @param type 消息类型 |    * @param type 消息类型 | ||||||
|    */ |    */ | ||||||
|   const formatUnreadLength = (type: string) => { |   const formatUnreadCount = (type: number) => { | ||||||
|     const unreadList = getUnreadList(type); |     const count = unreadCount.value?.details.find( | ||||||
|     return unreadList.length ? `(${unreadList.length})` : ``; |       (item) => item.type === type | ||||||
|  |     )?.count; | ||||||
|  |     return count && count !== 0 ? `(${count})` : ''; | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -110,10 +98,10 @@ | |||||||
|    * |    * | ||||||
|    * @param items 消息 |    * @param items 消息 | ||||||
|    */ |    */ | ||||||
|   const handleItemClick = (items: MessageListType) => { |   const handleItemClick = (items: DataRecord[]) => { | ||||||
|     if (renderList.value.length) readMessage([...items]); |     if (messageList.value.length) readMessage([...items]); | ||||||
|   }; |   }; | ||||||
|   fetchSourceData(); |   getList(); | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped lang="less"> | <style scoped lang="less"> | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ | |||||||
|       :key="item.id" |       :key="item.id" | ||||||
|       action-layout="vertical" |       action-layout="vertical" | ||||||
|       :style="{ |       :style="{ | ||||||
|         opacity: item.readStatus ? 0.5 : 1, |         opacity: item.isRead ? 0.5 : 1, | ||||||
|       }" |       }" | ||||||
|     > |     > | ||||||
|       <div class="item-wrap" @click="onItemClick(item)"> |       <div class="item-wrap" @click="onItemClick(item)"> | ||||||
| @@ -13,9 +13,6 @@ | |||||||
|           <template #title> |           <template #title> | ||||||
|             <a-space :size="4"> |             <a-space :size="4"> | ||||||
|               <span>{{ item.title }}</span> |               <span>{{ item.title }}</span> | ||||||
|               <a-typography-text type="secondary"> |  | ||||||
|                 {{ item.subTitle }} |  | ||||||
|               </a-typography-text> |  | ||||||
|             </a-space> |             </a-space> | ||||||
|           </template> |           </template> | ||||||
|           <template #description> |           <template #description> | ||||||
| @@ -41,10 +38,12 @@ | |||||||
|         :class="{ 'add-border-top': renderList.length < showMax }" |         :class="{ 'add-border-top': renderList.length < showMax }" | ||||||
|       > |       > | ||||||
|         <div class="footer-wrap"> |         <div class="footer-wrap"> | ||||||
|           <a-link @click="allRead">{{ $t('messageBox.allRead') }}</a-link> |           <a-link @click="handleReadAll">{{ $t('messageBox.allRead') }}</a-link> | ||||||
|         </div> |         </div> | ||||||
|         <div class="footer-wrap"> |         <div class="footer-wrap"> | ||||||
|           <a-link @click="toList">{{ $t('messageBox.viewMore') }}</a-link> |           <a-link @click="handleViewMore">{{ | ||||||
|  |             $t('messageBox.viewMore') | ||||||
|  |           }}</a-link> | ||||||
|         </div> |         </div> | ||||||
|       </a-space> |       </a-space> | ||||||
|     </template> |     </template> | ||||||
| @@ -58,33 +57,28 @@ | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { PropType } from 'vue'; |   import { PropType } from 'vue'; | ||||||
|   import { useRouter } from 'vue-router'; |   import { useRouter } from 'vue-router'; | ||||||
|   import { MessageRecord, MessageListType } from '@/api/system/message'; |   import { DataRecord } from '@/api/system/message'; | ||||||
|  |  | ||||||
|   const router = useRouter(); |   const router = useRouter(); | ||||||
|  |  | ||||||
|   const props = defineProps({ |   const props = defineProps({ | ||||||
|     renderList: { |     renderList: { | ||||||
|       type: Array as PropType<MessageListType>, |       type: Array as PropType<DataRecord[]>, | ||||||
|       required: true, |       required: true, | ||||||
|     }, |     }, | ||||||
|     unreadCount: { |  | ||||||
|       type: Number, |  | ||||||
|       default: 0, |  | ||||||
|     }, |  | ||||||
|   }); |   }); | ||||||
|   const emit = defineEmits(['itemClick']); |   const emit = defineEmits(['itemClick']); | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 全部已读 |    * 全部已读 | ||||||
|    */ |    */ | ||||||
|   const allRead = () => { |   const handleReadAll = () => { | ||||||
|     emit('itemClick', [...props.renderList]); |     emit('itemClick', [...props.renderList]); | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 查看更多 |    * 查看更多 | ||||||
|    */ |    */ | ||||||
|   const toList = () => { |   const handleViewMore = () => { | ||||||
|     router.push({ |     router.push({ | ||||||
|       name: 'Message', |       name: 'Message', | ||||||
|     }); |     }); | ||||||
| @@ -92,10 +86,11 @@ | |||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 点击消息 |    * 点击消息 | ||||||
|  |    * | ||||||
|    * @param item 消息 |    * @param item 消息 | ||||||
|    */ |    */ | ||||||
|   const onItemClick = (item: MessageRecord) => { |   const onItemClick = (item: DataRecord) => { | ||||||
|     if (!item.readStatus) { |     if (!item.isRead) { | ||||||
|       emit('itemClick', [item]); |       emit('itemClick', [item]); | ||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| export default { | export default { | ||||||
|   'messageBox.tab.title.message': 'Message', |   'messageBox.tab.title.message.system': 'System Message', | ||||||
|   'messageBox.tab.title.notice': 'Notice', |  | ||||||
|   'messageBox.tab.title.todo': 'Todo', |  | ||||||
|   'messageBox.tab.button': 'empty', |   'messageBox.tab.button': 'empty', | ||||||
|   'messageBox.allRead': 'All Read', |   'messageBox.allRead': 'All Read', | ||||||
|   'messageBox.viewMore': 'View More', |   'messageBox.viewMore': 'View More', | ||||||
|   | |||||||
| @@ -1,7 +1,5 @@ | |||||||
| export default { | export default { | ||||||
|   'messageBox.tab.title.message': '消息', |   'messageBox.tab.title.message.system': '系统消息', | ||||||
|   'messageBox.tab.title.notice': '通知', |  | ||||||
|   'messageBox.tab.title.todo': '待办', |  | ||||||
|   'messageBox.tab.button': '清空', |   'messageBox.tab.button': '清空', | ||||||
|   'messageBox.allRead': '全部已读', |   'messageBox.allRead': '全部已读', | ||||||
|   'messageBox.viewMore': '查看更多', |   'messageBox.viewMore': '查看更多', | ||||||
|   | |||||||
| @@ -193,7 +193,7 @@ | |||||||
|   import { computed, ref, inject, watchEffect } from 'vue'; |   import { computed, ref, inject, watchEffect } from 'vue'; | ||||||
|   import { useDark, useToggle, useFullscreen } from '@vueuse/core'; |   import { useDark, useToggle, useFullscreen } from '@vueuse/core'; | ||||||
|   import { useAppStore, useUserStore } from '@/store'; |   import { useAppStore, useUserStore } from '@/store'; | ||||||
|   import { list } from '@/api/system/message'; |   import { countUnread } from '@/api/system/message'; | ||||||
|   import { LOCALE_OPTIONS } from '@/locale'; |   import { LOCALE_OPTIONS } from '@/locale'; | ||||||
|   import useLocale from '@/hooks/locale'; |   import useLocale from '@/hooks/locale'; | ||||||
|   import useUser from '@/hooks/user'; |   import useUser from '@/hooks/user'; | ||||||
| @@ -224,12 +224,17 @@ | |||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|   const toggleTheme = useToggle(isDark); |   const toggleTheme = useToggle(isDark); | ||||||
|  |  | ||||||
|   const unReadMessageCount = ref(0); |   const unReadMessageCount = ref(0); | ||||||
|   watchEffect(async () => { |  | ||||||
|     const res = await list({ sort: ['createTime,desc'], readStatus: 0 }); |   /** | ||||||
|     unReadMessageCount.value = res.data?.length ?? 0; |    * 查询未读消息总数 | ||||||
|   }); |    */ | ||||||
|  |   async function getUnreadCount() { | ||||||
|  |     const res = await countUnread(false); | ||||||
|  |     unReadMessageCount.value = res.data.total; | ||||||
|  |   } | ||||||
|  |   getUnreadCount(); | ||||||
|  |   setInterval(getUnreadCount, 10000); | ||||||
|  |  | ||||||
|   const handleToggleTheme = () => { |   const handleToggleTheme = () => { | ||||||
|     toggleTheme(); |     toggleTheme(); | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import localeRole from '@/views/system/role/locale/en-US'; | |||||||
| import localeMenu from '@/views/system/menu/locale/en-US'; | import localeMenu from '@/views/system/menu/locale/en-US'; | ||||||
| import localeDept from '@/views/system/dept/locale/en-US'; | import localeDept from '@/views/system/dept/locale/en-US'; | ||||||
| import localeAnnouncement from '@/views/system/announcement/locale/en-US'; | import localeAnnouncement from '@/views/system/announcement/locale/en-US'; | ||||||
| import localeNotice from '@/views/system/message/locale/en-US'; | import localeMessage from '@/views/system/message/locale/en-US'; | ||||||
| import localeDict from '@/views/system/dict/locale/en-US'; | import localeDict from '@/views/system/dict/locale/en-US'; | ||||||
| import localeConfig from '@/views/system/config/locale/en-US'; | import localeConfig from '@/views/system/config/locale/en-US'; | ||||||
|  |  | ||||||
| @@ -63,7 +63,7 @@ export default { | |||||||
|   ...localeMenu, |   ...localeMenu, | ||||||
|   ...localeDept, |   ...localeDept, | ||||||
|   ...localeAnnouncement, |   ...localeAnnouncement, | ||||||
|   ...localeNotice, |   ...localeMessage, | ||||||
|   ...localeDict, |   ...localeDict, | ||||||
|   ...localeConfig, |   ...localeConfig, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ import localeRole from '@/views/system/role/locale/zh-CN'; | |||||||
| import localeMenu from '@/views/system/menu/locale/zh-CN'; | import localeMenu from '@/views/system/menu/locale/zh-CN'; | ||||||
| import localeDept from '@/views/system/dept/locale/zh-CN'; | import localeDept from '@/views/system/dept/locale/zh-CN'; | ||||||
| import localeAnnouncement from '@/views/system/announcement/locale/zh-CN'; | import localeAnnouncement from '@/views/system/announcement/locale/zh-CN'; | ||||||
| import locaoNotice from '@/views/system/message/locale/zh-CN'; | import localeMessage from '@/views/system/message/locale/zh-CN'; | ||||||
| import localeDict from '@/views/system/dict/locale/zh-CN'; | import localeDict from '@/views/system/dict/locale/zh-CN'; | ||||||
| import localeConfig from '@/views/system/config/locale/zh-CN'; | import localeConfig from '@/views/system/config/locale/zh-CN'; | ||||||
|  |  | ||||||
| @@ -63,7 +63,7 @@ export default { | |||||||
|   ...localeMenu, |   ...localeMenu, | ||||||
|   ...localeDept, |   ...localeDept, | ||||||
|   ...localeAnnouncement, |   ...localeAnnouncement, | ||||||
|   ...locaoNotice, |   ...localeMessage, | ||||||
|   ...localeDict, |   ...localeDict, | ||||||
|   ...localeConfig, |   ...localeConfig, | ||||||
|  |  | ||||||
|   | |||||||
| @@ -62,7 +62,7 @@ const System: AppRouteRecordRaw = { | |||||||
|       path: '/system/message', |       path: '/system/message', | ||||||
|       component: () => import('@/views/system/message/index.vue'), |       component: () => import('@/views/system/message/index.vue'), | ||||||
|       meta: { |       meta: { | ||||||
|         locale: 'menu.system.message', |         locale: 'menu.system.message.list', | ||||||
|         requiresAuth: true, |         requiresAuth: true, | ||||||
|       }, |       }, | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ | |||||||
|             <a-form-item field="title" hide-label> |             <a-form-item field="title" hide-label> | ||||||
|               <a-input |               <a-input | ||||||
|                 v-model="queryParams.title" |                 v-model="queryParams.title" | ||||||
|                 placeholder="输入主题搜索" |                 placeholder="输入标题搜索" | ||||||
|                 allow-clear |                 allow-clear | ||||||
|                 style="width: 230px" |                 style="width: 230px" | ||||||
|                 @press-enter="handleQuery" |                 @press-enter="handleQuery" | ||||||
| @@ -19,16 +19,15 @@ | |||||||
|             <a-form-item field="type" hide-label> |             <a-form-item field="type" hide-label> | ||||||
|               <a-select |               <a-select | ||||||
|                 v-model="queryParams.type" |                 v-model="queryParams.type" | ||||||
|                 :options="message_type" |                 :options="message_type_enum" | ||||||
|                 placeholder="类型搜索" |                 placeholder="类型搜索" | ||||||
|                 allow-clear |                 allow-clear | ||||||
|                 style="width: 150px" |                 style="width: 150px" | ||||||
|               /> |               /> | ||||||
|             </a-form-item> |             </a-form-item> | ||||||
|  |             <a-form-item field="isRead" hide-label> | ||||||
|             <a-form-item field="type" hide-label> |  | ||||||
|               <a-select |               <a-select | ||||||
|                 v-model="queryParams.readStatus" |                 v-model="queryParams.isRead" | ||||||
|                 :style="{ width: '150px' }" |                 :style="{ width: '150px' }" | ||||||
|                 placeholder="是否已读" |                 placeholder="是否已读" | ||||||
|                 allow-clear |                 allow-clear | ||||||
| @@ -58,7 +57,7 @@ | |||||||
|                   type="primary" |                   type="primary" | ||||||
|                   status="success" |                   status="success" | ||||||
|                   :disabled="readMultiple" |                   :disabled="readMultiple" | ||||||
|                   :title="readMultiple ? '请选择要读取的数据' : ''" |                   :title="readMultiple ? '请选择数据' : ''" | ||||||
|                   @click="handleBatchRedaMessage" |                   @click="handleBatchRedaMessage" | ||||||
|                 > |                 > | ||||||
|                   <template #icon><icon-check /></template>标记已读 |                   <template #icon><icon-check /></template>标记已读 | ||||||
| @@ -123,33 +122,27 @@ | |||||||
|               {{ rowIndex + 1 + (queryParams.page - 1) * queryParams.size }} |               {{ rowIndex + 1 + (queryParams.page - 1) * queryParams.size }} | ||||||
|             </template> |             </template> | ||||||
|           </a-table-column> |           </a-table-column> | ||||||
|           <a-table-column title="主题"> |           <a-table-column title="标题"> | ||||||
|             <template #cell="{ record }"> |             <template #cell="{ record }"> | ||||||
|               <a-link @click="toDetail(record.id)">{{ record.title }}</a-link> |               <a-link @click="toDetail(record)">{{ record.title }}</a-link> | ||||||
|             </template> |             </template> | ||||||
|           </a-table-column> |           </a-table-column> | ||||||
|           <a-table-column title="类型" align="center"> |           <a-table-column title="类型" align="center"> | ||||||
|             <template #cell="{ record }"> |             <template #cell="{ record }"> | ||||||
|               <dict-tag :value="record.type" :dict="message_type" /> |               <dict-tag :value="record.type" :dict="message_type_enum" /> | ||||||
|             </template> |             </template> | ||||||
|           </a-table-column> |           </a-table-column> | ||||||
|           <a-table-column title="是否已读" align="center"> |           <a-table-column title="是否已读" align="center"> | ||||||
|             <template #cell="{ record }"> |             <template #cell="{ record }"> | ||||||
|               <a-tag v-if="record.readStatus" color="green">是</a-tag> |               <a-tag v-if="record.isRead" color="green">是</a-tag> | ||||||
|               <a-tag v-else color="red">否</a-tag> |               <a-tag v-else color="red">否</a-tag> | ||||||
|             </template> |             </template> | ||||||
|           </a-table-column> |           </a-table-column> | ||||||
|           <a-table-column title="发送时间" data-index="createTime" /> |           <a-table-column title="发送时间" data-index="createTime" /> | ||||||
|           <a-table-column |           <a-table-column title="操作" align="center"> | ||||||
|             v-if=" |  | ||||||
|               checkPermission(['system:message:delete']) |  | ||||||
|             " |  | ||||||
|             title="操作" |  | ||||||
|             align="center" |  | ||||||
|           > |  | ||||||
|             <template #cell="{ record }"> |             <template #cell="{ record }"> | ||||||
|               <a-button |               <a-button | ||||||
|                 :disabled="record.readStatus" |                 :disabled="record.isRead" | ||||||
|                 type="text" |                 type="text" | ||||||
|                 size="small" |                 size="small" | ||||||
|                 title="标记已读" |                 title="标记已读" | ||||||
| @@ -157,7 +150,6 @@ | |||||||
|               > |               > | ||||||
|                 <template #icon><icon-check /></template>标记已读 |                 <template #icon><icon-check /></template>标记已读 | ||||||
|               </a-button> |               </a-button> | ||||||
|  |  | ||||||
|               <a-popconfirm |               <a-popconfirm | ||||||
|                 content="确定要删除当前选中的数据吗?" |                 content="确定要删除当前选中的数据吗?" | ||||||
|                 type="warning" |                 type="warning" | ||||||
| @@ -189,60 +181,47 @@ | |||||||
|         @cancel="handleDetailCancel" |         @cancel="handleDetailCancel" | ||||||
|       > |       > | ||||||
|         <a-descriptions :column="2" bordered size="large"> |         <a-descriptions :column="2" bordered size="large"> | ||||||
|           <a-descriptions-item label="主题"> |           <a-descriptions-item label="标题"> | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|               <a-skeleton-line :rows="1" /> |               <a-skeleton-line :rows="1" /> | ||||||
|             </a-skeleton> |             </a-skeleton> | ||||||
|             <span v-else>{{ dataDetail.title }}</span> |             <span v-else>{{ dataDetail.title }}</span> | ||||||
|           </a-descriptions-item> |           </a-descriptions-item> | ||||||
|  |  | ||||||
|           <a-descriptions-item label="类型"> |           <a-descriptions-item label="类型"> | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|               <a-skeleton-line :rows="1" /> |               <a-skeleton-line :rows="1" /> | ||||||
|             </a-skeleton> |             </a-skeleton> | ||||||
|             <span v-else> |             <span v-else> | ||||||
|               <dict-tag :value="dataDetail.type" :dict="message_type" /> |               <dict-tag :value="dataDetail.type" :dict="message_type_enum" /> | ||||||
|             </span> |             </span> | ||||||
|           </a-descriptions-item> |           </a-descriptions-item> | ||||||
|  |  | ||||||
|           <a-descriptions-item label="发送人"> |  | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |  | ||||||
|               <a-skeleton-line :rows="1" /> |  | ||||||
|             </a-skeleton> |  | ||||||
|             <span v-else-if="dataDetail.createUserString">{{ |  | ||||||
|               dataDetail.createUserString |  | ||||||
|             }}</span> |  | ||||||
|             <dict-tag |  | ||||||
|               v-if="dataDetail.createUserString == null" |  | ||||||
|               :value="dataDetail.type" |  | ||||||
|               :dict="message_type" |  | ||||||
|             /> |  | ||||||
|           </a-descriptions-item> |  | ||||||
|  |  | ||||||
|           <a-descriptions-item label="发送时间"> |  | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |  | ||||||
|               <a-skeleton-line :rows="1" /> |  | ||||||
|             </a-skeleton> |  | ||||||
|             <span v-else>{{ dataDetail.createTime }}</span> |  | ||||||
|           </a-descriptions-item> |  | ||||||
|  |  | ||||||
|           <a-descriptions-item label="是否已读"> |           <a-descriptions-item label="是否已读"> | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|               <a-skeleton-line :rows="1" /> |               <a-skeleton-line :rows="1" /> | ||||||
|             </a-skeleton> |             </a-skeleton> | ||||||
|             <span v-else> |             <span v-else> | ||||||
|               <a-tag v-if="dataDetail.readStatus" color="green">是</a-tag> |               <a-tag v-if="dataDetail.isRead" color="green">是</a-tag> | ||||||
|               <a-tag v-else color="red">否</a-tag> |               <a-tag v-else color="red">否</a-tag> | ||||||
|             </span> |             </span> | ||||||
|           </a-descriptions-item> |           </a-descriptions-item> | ||||||
|  |  | ||||||
|           <a-descriptions-item label="读取时间"> |           <a-descriptions-item label="读取时间"> | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|               <a-skeleton-line :rows="1" /> |               <a-skeleton-line :rows="1" /> | ||||||
|             </a-skeleton> |             </a-skeleton> | ||||||
|             <span v-else>{{ dataDetail.readTime }}</span> |             <span v-else>{{ dataDetail.readTime }}</span> | ||||||
|           </a-descriptions-item> |           </a-descriptions-item> | ||||||
|  |           <a-descriptions-item label="发送人"> | ||||||
|  |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|  |               <a-skeleton-line :rows="1" /> | ||||||
|  |             </a-skeleton> | ||||||
|  |             <span v-else>{{ dataDetail.createUserString }}</span> | ||||||
|  |           </a-descriptions-item> | ||||||
|  |           <a-descriptions-item label="发送时间"> | ||||||
|  |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|  |               <a-skeleton-line :rows="1" /> | ||||||
|  |             </a-skeleton> | ||||||
|  |             <span v-else>{{ dataDetail.createTime }}</span> | ||||||
|  |           </a-descriptions-item> | ||||||
|           <a-descriptions-item label="内容"> |           <a-descriptions-item label="内容"> | ||||||
|             <a-skeleton v-if="detailLoading" :animation="true"> |             <a-skeleton v-if="detailLoading" :animation="true"> | ||||||
|               <a-skeleton-line :rows="1" /> |               <a-skeleton-line :rows="1" /> | ||||||
| @@ -257,29 +236,19 @@ | |||||||
|  |  | ||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
|   import { getCurrentInstance, ref, toRefs, reactive } from 'vue'; |   import { getCurrentInstance, ref, toRefs, reactive } from 'vue'; | ||||||
|   import { |   import { DataRecord, ListParam, list, del, read } from '@/api/system/message'; | ||||||
|     MessageRecord, |  | ||||||
|     page, |  | ||||||
|     ListParam, |  | ||||||
|     get, |  | ||||||
|     del, |  | ||||||
|     read, |  | ||||||
|   } from '@/api/system/message'; |  | ||||||
|   import checkPermission from '@/utils/permission'; |  | ||||||
|  |  | ||||||
|   const { proxy } = getCurrentInstance() as any; |   const { proxy } = getCurrentInstance() as any; | ||||||
|   const { message_type } = proxy.useDict('message_type'); |   const { message_type_enum } = proxy.useDict('message_type_enum'); | ||||||
|  |   const dataList = ref<DataRecord[]>([]); | ||||||
|   const dataList = ref<MessageRecord[]>([]); |   const dataDetail = ref<DataRecord>({ | ||||||
|   const dataDetail = ref<MessageRecord>({ |  | ||||||
|     id: 0, |     id: 0, | ||||||
|     title: '', |     title: '', | ||||||
|     content: '', |     content: '', | ||||||
|     type: '', |     type: 1, | ||||||
|     createUserString: '', |     createUserString: '', | ||||||
|     createTime: '', |     createTime: '', | ||||||
|     subTitle: '', |     isRead: false, | ||||||
|     readStatus: false, |  | ||||||
|     readTime: '', |     readTime: '', | ||||||
|   }); |   }); | ||||||
|   const total = ref(0); |   const total = ref(0); | ||||||
| @@ -296,21 +265,22 @@ | |||||||
|     // 查询参数 |     // 查询参数 | ||||||
|     queryParams: { |     queryParams: { | ||||||
|       title: undefined, |       title: undefined, | ||||||
|       readStatus: undefined, |  | ||||||
|       type: undefined, |       type: undefined, | ||||||
|  |       isRead: undefined, | ||||||
|       page: 1, |       page: 1, | ||||||
|       size: 10, |       size: 10, | ||||||
|       sort: ['createTime,desc'], |       sort: ['isRead,asc', 'createTime,desc'], | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|   const { queryParams } = toRefs(data); |   const { queryParams } = toRefs(data); | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|    * 查询列表 |    * 查询列表 | ||||||
|    * |    * | ||||||
|    */ |    */ | ||||||
|   const getList = (params: ListParam = { ...queryParams.value }) => { |   const getList = (params: ListParam = { ...queryParams.value }) => { | ||||||
|     loading.value = true; |     loading.value = true; | ||||||
|     page(params) |     list(params) | ||||||
|       .then((res) => { |       .then((res) => { | ||||||
|         dataList.value = res.data.list; |         dataList.value = res.data.list; | ||||||
|         total.value = res.data.total; |         total.value = res.data.total; | ||||||
| @@ -324,13 +294,11 @@ | |||||||
|   /** |   /** | ||||||
|    * 查看详情 |    * 查看详情 | ||||||
|    * |    * | ||||||
|    * @param id ID |    * @param record 记录信息 | ||||||
|    */ |    */ | ||||||
|   const toDetail = async (id: number) => { |   const toDetail = async (record: DataRecord) => { | ||||||
|     detailVisible.value = true; |     detailVisible.value = true; | ||||||
|     get(id).then((res) => { |     dataDetail.value = record; | ||||||
|       dataDetail.value = res.data; |  | ||||||
|     }); |  | ||||||
|   }; |   }; | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -377,7 +345,7 @@ | |||||||
|    */ |    */ | ||||||
|   const handleBatchRedaMessage = () => { |   const handleBatchRedaMessage = () => { | ||||||
|     if (ids.value.length === 0) { |     if (ids.value.length === 0) { | ||||||
|       proxy.$message.info('请选择要读取的数据'); |       proxy.$message.info('请选择数据'); | ||||||
|     } else { |     } else { | ||||||
|       handleReadMessage(ids.value); |       handleReadMessage(ids.value); | ||||||
|     } |     } | ||||||
| @@ -410,7 +378,7 @@ | |||||||
|    */ |    */ | ||||||
|   const handleSelectionChange = (rowKeys: Array<any>) => { |   const handleSelectionChange = (rowKeys: Array<any>) => { | ||||||
|     const unReadMessageList = dataList.value.filter( |     const unReadMessageList = dataList.value.filter( | ||||||
|       (item) => rowKeys.indexOf(item.id) !== -1 && !item.readStatus |       (item) => rowKeys.indexOf(item.id) !== -1 && !item.isRead | ||||||
|     ); |     ); | ||||||
|     readMultiple.value = !unReadMessageList.length; |     readMultiple.value = !unReadMessageList.length; | ||||||
|     ids.value = rowKeys; |     ids.value = rowKeys; | ||||||
| @@ -460,25 +428,4 @@ | |||||||
|   }; |   }; | ||||||
| </script> | </script> | ||||||
|  |  | ||||||
| <style scoped lang="less"> | <style scoped lang="less"></style> | ||||||
|   :deep(.github-markdown-body) { |  | ||||||
|     padding: 16px 32px 5px; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   :deep(.arco-form-item-label-tooltip) { |  | ||||||
|     margin-left: 3px; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .meta-data { |  | ||||||
|     font-size: 15px; |  | ||||||
|     text-align: center; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .icon { |  | ||||||
|     margin-right: 3px; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   .update-time-row { |  | ||||||
|     text-align: right; |  | ||||||
|   } |  | ||||||
| </style> |  | ||||||
|   | |||||||
| @@ -16,8 +16,6 @@ | |||||||
|  |  | ||||||
| package top.charles7c.cnadmin.webapi.controller.system; | package top.charles7c.cnadmin.webapi.controller.system; | ||||||
|  |  | ||||||
| import static top.charles7c.cnadmin.common.annotation.CrudRequestMapping.Api; |  | ||||||
|  |  | ||||||
| import java.util.List; | import java.util.List; | ||||||
|  |  | ||||||
| import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||||
| @@ -27,14 +25,16 @@ import io.swagger.v3.oas.annotations.Parameter; | |||||||
| import io.swagger.v3.oas.annotations.enums.ParameterIn; | import io.swagger.v3.oas.annotations.enums.ParameterIn; | ||||||
| import io.swagger.v3.oas.annotations.tags.Tag; | import io.swagger.v3.oas.annotations.tags.Tag; | ||||||
|  |  | ||||||
| import org.springframework.web.bind.annotation.PatchMapping; | import org.springframework.validation.annotation.Validated; | ||||||
| import org.springframework.web.bind.annotation.RequestParam; | import org.springframework.web.bind.annotation.*; | ||||||
| import org.springframework.web.bind.annotation.RestController; |  | ||||||
|  |  | ||||||
| import top.charles7c.cnadmin.common.annotation.CrudRequestMapping; | import top.charles7c.cnadmin.common.model.query.PageQuery; | ||||||
| import top.charles7c.cnadmin.common.base.BaseController; | import top.charles7c.cnadmin.common.model.vo.PageDataVO; | ||||||
|  | import top.charles7c.cnadmin.common.model.vo.R; | ||||||
|  | import top.charles7c.cnadmin.common.util.helper.LoginHelper; | ||||||
|  | import top.charles7c.cnadmin.monitor.annotation.Log; | ||||||
| import top.charles7c.cnadmin.system.model.query.MessageQuery; | import top.charles7c.cnadmin.system.model.query.MessageQuery; | ||||||
| import top.charles7c.cnadmin.system.model.request.MessageRequest; | import top.charles7c.cnadmin.system.model.vo.MessageUnreadVO; | ||||||
| import top.charles7c.cnadmin.system.model.vo.MessageVO; | import top.charles7c.cnadmin.system.model.vo.MessageVO; | ||||||
| import top.charles7c.cnadmin.system.service.MessageService; | import top.charles7c.cnadmin.system.service.MessageService; | ||||||
| import top.charles7c.cnadmin.system.service.MessageUserService; | import top.charles7c.cnadmin.system.service.MessageUserService; | ||||||
| @@ -48,16 +48,39 @@ import top.charles7c.cnadmin.system.service.MessageUserService; | |||||||
| @Tag(name = "消息管理 API") | @Tag(name = "消息管理 API") | ||||||
| @RestController | @RestController | ||||||
| @RequiredArgsConstructor | @RequiredArgsConstructor | ||||||
| @CrudRequestMapping(value = "/system/message", api = {Api.PAGE, Api.GET, Api.DELETE, Api.LIST}) | @RequestMapping("/system/message") | ||||||
| public class MessageController | public class MessageController { | ||||||
|     extends BaseController<MessageService, MessageVO, MessageVO, MessageQuery, MessageRequest> { |  | ||||||
|  |  | ||||||
|  |     private final MessageService baseService; | ||||||
|     private final MessageUserService messageUserService; |     private final MessageUserService messageUserService; | ||||||
|  |  | ||||||
|  |     @Operation(summary = "分页查询列表", description = "分页查询列表") | ||||||
|  |     @GetMapping | ||||||
|  |     public PageDataVO<MessageVO> page(MessageQuery query, @Validated PageQuery pageQuery) { | ||||||
|  |         query.setUserId(LoginHelper.getUserId()); | ||||||
|  |         return baseService.page(query, pageQuery); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Operation(summary = "删除数据", description = "删除数据") | ||||||
|  |     @Parameter(name = "ids", description = "ID 列表", example = "1,2", in = ParameterIn.PATH) | ||||||
|  |     @DeleteMapping("/{ids}") | ||||||
|  |     public R delete(@PathVariable List<Long> ids) { | ||||||
|  |         baseService.delete(ids); | ||||||
|  |         return R.ok("删除成功"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     @Operation(description = "标记已读", summary = "将消息标记为已读状态") |     @Operation(description = "标记已读", summary = "将消息标记为已读状态") | ||||||
|     @Parameter(name = "ids", description = "消息ID列表", example = "1,2", in = ParameterIn.QUERY) |     @Parameter(name = "ids", description = "消息ID列表", example = "1,2", in = ParameterIn.QUERY) | ||||||
|     @PatchMapping("/read") |     @PatchMapping("/read") | ||||||
|     public void readMessage(@RequestParam(required = false) List<Long> ids) { |     public void readMessage(@RequestParam(required = false) List<Long> ids) { | ||||||
|         messageUserService.readMessage(ids); |         messageUserService.readMessage(ids); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     @Log(ignore = true) | ||||||
|  |     @Operation(description = "查询未读消息数量", summary = "查询当前用户的未读消息数量") | ||||||
|  |     @Parameter(name = "isDetail", description = "是否查询详情", example = "true", in = ParameterIn.QUERY) | ||||||
|  |     @GetMapping("/unread") | ||||||
|  |     public MessageUnreadVO countUnreadMessage(@RequestParam(required = false) Boolean detail) { | ||||||
|  |         return messageUserService.countUnreadMessageByUserId(LoginHelper.getUserId(), detail); | ||||||
|  |     } | ||||||
| } | } | ||||||
| @@ -7,14 +7,3 @@ INSERT IGNORE INTO `sys_menu` | |||||||
| VALUES | VALUES | ||||||
| (1060, '消息管理', 1000, 2, '/system/message', 'Message', 'system/message/index', 'notification', b'0', b'0', b'0', 'system:message:list', 6, 1, 1, NOW(), NULL, NULL), | (1060, '消息管理', 1000, 2, '/system/message', 'Message', 'system/message/index', 'notification', b'0', b'0', b'0', 'system:message:list', 6, 1, 1, NOW(), NULL, NULL), | ||||||
| (1061, '消息删除', 1060, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:message:delete', 1, 1, 1, NOW(), NULL, NULL); | (1061, '消息删除', 1060, 3, NULL, NULL, NULL, NULL, b'0', b'0', b'0', 'system:message:delete', 1, 1, 1, NOW(), NULL, NULL); | ||||||
|  |  | ||||||
| -- 初始化默认字典 |  | ||||||
| INSERT IGNORE INTO `sys_dict` |  | ||||||
| (`id`, `name`, `code`, `description`, `is_system`, `create_user`, `create_time`, `update_user`, `update_time`) |  | ||||||
| VALUES |  | ||||||
| (2, '消息类型', 'message_type', NULL, b'1', 1, NOW(), NULL, NULL); |  | ||||||
|  |  | ||||||
| INSERT IGNORE INTO `sys_dict_item` |  | ||||||
| (`id`, `label`, `value`, `color`, `sort`, `description`, `dict_id`, `create_user`, `create_time`, `update_user`, `update_time`) |  | ||||||
| VALUES |  | ||||||
| (3, '系统消息', '1', 'blue', 1, NULL, 2, 1, NOW(), NULL, NULL); |  | ||||||
|   | |||||||
| @@ -14,9 +14,9 @@ CREATE TABLE IF NOT EXISTS `sys_user_social` ( | |||||||
| -- changeset BUSS_BCLS:2 | -- changeset BUSS_BCLS:2 | ||||||
| CREATE TABLE IF NOT EXISTS `sys_message` ( | CREATE TABLE IF NOT EXISTS `sys_message` ( | ||||||
|     `id`          bigint(20)   AUTO_INCREMENT              COMMENT 'ID', |     `id`          bigint(20)   AUTO_INCREMENT              COMMENT 'ID', | ||||||
|     `title`       varchar(50)  NOT NULL       COMMENT '主题', |     `title`       varchar(50)  NOT NULL                    COMMENT '标题', | ||||||
|     `content`     varchar(255) DEFAULT NULL                COMMENT '内容', |     `content`     varchar(255) DEFAULT NULL                COMMENT '内容', | ||||||
|     `type`        varchar(30)  NOT NULL       COMMENT '类型', |     `type`        tinyint(1)   UNSIGNED NOT NULL DEFAULT 1 COMMENT '类型(1:系统消息)', | ||||||
|     `create_user` bigint(20)   DEFAULT NULL                COMMENT '创建人', |     `create_user` bigint(20)   DEFAULT NULL                COMMENT '创建人', | ||||||
|     `create_time` datetime     NOT NULL                    COMMENT '创建时间', |     `create_time` datetime     NOT NULL                    COMMENT '创建时间', | ||||||
|     PRIMARY KEY (`id`) USING BTREE |     PRIMARY KEY (`id`) USING BTREE | ||||||
| @@ -25,7 +25,7 @@ CREATE TABLE IF NOT EXISTS `sys_message` ( | |||||||
| CREATE TABLE IF NOT EXISTS `sys_message_user` ( | CREATE TABLE IF NOT EXISTS `sys_message_user` ( | ||||||
|     `message_id` bigint(20) NOT NULL              COMMENT '消息ID', |     `message_id` bigint(20) NOT NULL              COMMENT '消息ID', | ||||||
|     `user_id`    bigint(11) NOT NULL              COMMENT '用户ID', |     `user_id`    bigint(11) NOT NULL              COMMENT '用户ID', | ||||||
|     `read_status` bit(1)     NOT NULL DEFAULT b'0' COMMENT '是否已读', |     `is_read`    bit(1)     NOT NULL DEFAULT b'0' COMMENT '是否已读', | ||||||
|     `read_time`  datetime   DEFAULT NULL          COMMENT '读取时间', |     `read_time`  datetime   DEFAULT NULL          COMMENT '读取时间', | ||||||
|     PRIMARY KEY (`message_id`,`user_id`) USING BTREE |     PRIMARY KEY (`message_id`,`user_id`) USING BTREE | ||||||
| ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息和用户关联表'; | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='消息和用户关联表'; | ||||||
		Reference in New Issue
	
	Block a user