Browse Source

!10 增加统计指标

Merge pull request !10 from blue/v_3.0.0
master
blue 6 months ago
committed by Gitee
parent
commit
b65746fb29
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 11
      im-admin-ui/src/api/im/group/index.ts
  2. 11
      im-admin-ui/src/api/im/groupMessage/index.ts
  3. 13
      im-admin-ui/src/api/im/privateMessage/index.ts
  4. 31
      im-admin-ui/src/api/im/user/index.ts
  5. BIN
      im-admin-ui/src/assets/emoji/0.gif
  6. BIN
      im-admin-ui/src/assets/emoji/1.gif
  7. BIN
      im-admin-ui/src/assets/emoji/10.gif
  8. BIN
      im-admin-ui/src/assets/emoji/11.gif
  9. BIN
      im-admin-ui/src/assets/emoji/12.gif
  10. BIN
      im-admin-ui/src/assets/emoji/13.gif
  11. BIN
      im-admin-ui/src/assets/emoji/14.gif
  12. BIN
      im-admin-ui/src/assets/emoji/15.gif
  13. BIN
      im-admin-ui/src/assets/emoji/16.gif
  14. BIN
      im-admin-ui/src/assets/emoji/17.gif
  15. BIN
      im-admin-ui/src/assets/emoji/18.gif
  16. BIN
      im-admin-ui/src/assets/emoji/19.gif
  17. BIN
      im-admin-ui/src/assets/emoji/2.gif
  18. BIN
      im-admin-ui/src/assets/emoji/20.gif
  19. BIN
      im-admin-ui/src/assets/emoji/21.gif
  20. BIN
      im-admin-ui/src/assets/emoji/22.gif
  21. BIN
      im-admin-ui/src/assets/emoji/23.gif
  22. BIN
      im-admin-ui/src/assets/emoji/24.gif
  23. BIN
      im-admin-ui/src/assets/emoji/25.gif
  24. BIN
      im-admin-ui/src/assets/emoji/26.gif
  25. BIN
      im-admin-ui/src/assets/emoji/27.gif
  26. BIN
      im-admin-ui/src/assets/emoji/28.gif
  27. BIN
      im-admin-ui/src/assets/emoji/29.gif
  28. BIN
      im-admin-ui/src/assets/emoji/3.gif
  29. BIN
      im-admin-ui/src/assets/emoji/30.gif
  30. BIN
      im-admin-ui/src/assets/emoji/31.gif
  31. BIN
      im-admin-ui/src/assets/emoji/32.gif
  32. BIN
      im-admin-ui/src/assets/emoji/33.gif
  33. BIN
      im-admin-ui/src/assets/emoji/34.gif
  34. BIN
      im-admin-ui/src/assets/emoji/35.gif
  35. BIN
      im-admin-ui/src/assets/emoji/36.gif
  36. BIN
      im-admin-ui/src/assets/emoji/37.gif
  37. BIN
      im-admin-ui/src/assets/emoji/38.gif
  38. BIN
      im-admin-ui/src/assets/emoji/39.gif
  39. BIN
      im-admin-ui/src/assets/emoji/4.gif
  40. BIN
      im-admin-ui/src/assets/emoji/40.gif
  41. BIN
      im-admin-ui/src/assets/emoji/41.gif
  42. BIN
      im-admin-ui/src/assets/emoji/42.gif
  43. BIN
      im-admin-ui/src/assets/emoji/43.gif
  44. BIN
      im-admin-ui/src/assets/emoji/44.gif
  45. BIN
      im-admin-ui/src/assets/emoji/45.gif
  46. BIN
      im-admin-ui/src/assets/emoji/46.gif
  47. BIN
      im-admin-ui/src/assets/emoji/47.gif
  48. BIN
      im-admin-ui/src/assets/emoji/48.gif
  49. BIN
      im-admin-ui/src/assets/emoji/49.gif
  50. BIN
      im-admin-ui/src/assets/emoji/5.gif
  51. BIN
      im-admin-ui/src/assets/emoji/50.gif
  52. BIN
      im-admin-ui/src/assets/emoji/51.gif
  53. BIN
      im-admin-ui/src/assets/emoji/52.gif
  54. BIN
      im-admin-ui/src/assets/emoji/53.gif
  55. BIN
      im-admin-ui/src/assets/emoji/54.gif
  56. BIN
      im-admin-ui/src/assets/emoji/55.gif
  57. BIN
      im-admin-ui/src/assets/emoji/56.gif
  58. BIN
      im-admin-ui/src/assets/emoji/6.gif
  59. BIN
      im-admin-ui/src/assets/emoji/7.gif
  60. BIN
      im-admin-ui/src/assets/emoji/8.gif
  61. BIN
      im-admin-ui/src/assets/emoji/9.gif
  62. 22
      im-admin-ui/src/components/im/ImMessageContent/index.vue
  63. 26
      im-admin-ui/src/utils/emotion.ts
  64. 10
      im-admin-ui/src/utils/str.ts
  65. 11
      im-admin-ui/src/utils/url.ts
  66. 8
      im-admin-ui/src/views/im/group/index.vue
  67. 16
      im-admin-ui/src/views/im/user/index.vue
  68. 547
      im-admin-ui/src/views/index.vue
  69. 6
      im-admin/pom.xml
  70. 30
      im-admin/ruoyi-admin/src/main/resources/application.yml
  71. 5
      im-admin/ruoyi-common/ruoyi-common-doc/pom.xml
  72. 9
      im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImGroupController.java
  73. 18
      im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImGroupMessageController.java
  74. 19
      im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImPrivateMessageController.java
  75. 35
      im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImUserController.java
  76. 16
      im-admin/ruoyi-im/src/main/java/org/dromara/im/mapper/ImGroupMessageMapper.java
  77. 16
      im-admin/ruoyi-im/src/main/java/org/dromara/im/mapper/ImPrivateMessageMapper.java
  78. 17
      im-admin/ruoyi-im/src/main/java/org/dromara/im/mapper/ImUserMapper.java
  79. 11
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImGroupMessageService.java
  80. 5
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImGroupService.java
  81. 11
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImPrivateMessageService.java
  82. 33
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImUserService.java
  83. 14
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImGroupMessageServiceImpl.java
  84. 9
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImGroupServiceImpl.java
  85. 14
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImPrivateMessageServiceImpl.java
  86. 64
      im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImUserServiceImpl.java

11
im-admin-ui/src/api/im/group/index.ts

@ -61,3 +61,14 @@ export const findGroupByName = (name?: String): AxiosPromise<GroupVO[]> => {
});
};
/**
*
*/
export const getTotalGroupCount = (): AxiosPromise<number> => {
return request({
url: '/im/group/totalCount',
method: 'get'
});
};

11
im-admin-ui/src/api/im/groupMessage/index.ts

@ -27,3 +27,14 @@ export const getGroupMessage = (id: string | number): AxiosPromise<GroupMessageV
});
};
/**
*
* @param days
*/
export const getDailyGroupMessageCount = (days?: number): AxiosPromise<any[]> => {
return request({
url: '/im/groupMessage/dailyCount',
method: 'get',
params: { days }
});
};

13
im-admin-ui/src/api/im/privateMessage/index.ts

@ -27,3 +27,16 @@ export const getPrivateMessage = (id: string | number): AxiosPromise<PrivateMess
});
};
/**
*
* @param days
*/
export const getDailyMessageCount = (days?: number): AxiosPromise<any[]> => {
return request({
url: '/im/privateMessage/dailyCount',
method: 'get',
params: { days }
});
};

31
im-admin-ui/src/api/im/user/index.ts

@ -61,3 +61,34 @@ export const findUserByName = (name?: String): AxiosPromise<UserVO[]> => {
});
};
/**
*
* @param days
*/
export const getDailyRegistrationCount = (days?: number): AxiosPromise<any[]> => {
return request({
url: '/im/user/dailyRegistrationCount',
method: 'get',
params: { days }
});
};
/**
*
*/
export const getTotalUserCount = (): AxiosPromise<number> => {
return request({
url: '/im/user/totalCount',
method: 'get'
});
};
/**
*
*/
export const getActiveUserStats = (): AxiosPromise<any> => {
return request({
url: '/im/user/activeStats',
method: 'get'
});
};

BIN
im-admin-ui/src/assets/emoji/0.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
im-admin-ui/src/assets/emoji/1.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
im-admin-ui/src/assets/emoji/10.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
im-admin-ui/src/assets/emoji/11.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
im-admin-ui/src/assets/emoji/12.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
im-admin-ui/src/assets/emoji/13.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
im-admin-ui/src/assets/emoji/14.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
im-admin-ui/src/assets/emoji/15.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
im-admin-ui/src/assets/emoji/16.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
im-admin-ui/src/assets/emoji/17.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
im-admin-ui/src/assets/emoji/18.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
im-admin-ui/src/assets/emoji/19.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
im-admin-ui/src/assets/emoji/2.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
im-admin-ui/src/assets/emoji/20.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
im-admin-ui/src/assets/emoji/21.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
im-admin-ui/src/assets/emoji/22.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

BIN
im-admin-ui/src/assets/emoji/23.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
im-admin-ui/src/assets/emoji/24.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
im-admin-ui/src/assets/emoji/25.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
im-admin-ui/src/assets/emoji/26.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
im-admin-ui/src/assets/emoji/27.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
im-admin-ui/src/assets/emoji/28.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
im-admin-ui/src/assets/emoji/29.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
im-admin-ui/src/assets/emoji/3.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
im-admin-ui/src/assets/emoji/30.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
im-admin-ui/src/assets/emoji/31.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
im-admin-ui/src/assets/emoji/32.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
im-admin-ui/src/assets/emoji/33.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
im-admin-ui/src/assets/emoji/34.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
im-admin-ui/src/assets/emoji/35.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
im-admin-ui/src/assets/emoji/36.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
im-admin-ui/src/assets/emoji/37.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
im-admin-ui/src/assets/emoji/38.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
im-admin-ui/src/assets/emoji/39.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
im-admin-ui/src/assets/emoji/4.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

BIN
im-admin-ui/src/assets/emoji/40.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
im-admin-ui/src/assets/emoji/41.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

BIN
im-admin-ui/src/assets/emoji/42.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

BIN
im-admin-ui/src/assets/emoji/43.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

BIN
im-admin-ui/src/assets/emoji/44.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

BIN
im-admin-ui/src/assets/emoji/45.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
im-admin-ui/src/assets/emoji/46.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
im-admin-ui/src/assets/emoji/47.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
im-admin-ui/src/assets/emoji/48.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
im-admin-ui/src/assets/emoji/49.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
im-admin-ui/src/assets/emoji/5.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
im-admin-ui/src/assets/emoji/50.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
im-admin-ui/src/assets/emoji/51.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
im-admin-ui/src/assets/emoji/52.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
im-admin-ui/src/assets/emoji/53.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
im-admin-ui/src/assets/emoji/54.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
im-admin-ui/src/assets/emoji/55.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

BIN
im-admin-ui/src/assets/emoji/56.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
im-admin-ui/src/assets/emoji/6.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
im-admin-ui/src/assets/emoji/7.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
im-admin-ui/src/assets/emoji/8.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

BIN
im-admin-ui/src/assets/emoji/9.gif

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

22
im-admin-ui/src/components/im/ImMessageContent/index.vue

@ -1,10 +1,12 @@
<template>
<div>
<div v-if="message.type == 1">
<image-preview :src="JSON.parse(message.content).thumbUrl" :full-src="JSON.parse(message.content).originUrl" :width="40" :height="40" />
<image-preview :src="JSON.parse(message.content).thumbUrl" :full-src="JSON.parse(message.content).originUrl"
:width="80" :height="80" />
</div>
<div v-else-if="message.type == 2">
<el-link type="primary" :href="JSON.parse(message.content).url" :download="JSON.parse(message.content).name" target="_blank">
<el-link type="primary" :href="JSON.parse(message.content).url" :download="JSON.parse(message.content).name"
target="_blank">
{{ JSON.parse(message.content).name }}
</el-link>
</div>
@ -12,16 +14,19 @@
<audio style="height: 40px;" controls :src="JSON.parse(message.content).url"></audio>
</div>
<div v-else-if="message.type == 4">
<video style="max-height: 120px;max-width: 200px;" controls :poster="JSON.parse(message.content).coverUrl" :src="JSON.parse(message.content).videoUrl"></video>
<video style="max-height: 120px;max-width: 200px;" controls :poster="JSON.parse(message.content).coverUrl"
:src="JSON.parse(message.content).videoUrl"></video>
</div>
<div v-else>
{{ message.content }}
<div v-else class="message-text" v-html="transform(replaceURLWithHTMLLinks(html2Escape(message.content)))">
</div>
</div>
</template>
<script setup lang="ts" name="ImMessageContent">
import { ref } from 'vue'
import { transform } from "@/utils/emotion"
import { replaceURLWithHTMLLinks } from "@/utils/url"
import { html2Escape } from "@/utils/str"
const props = defineProps({
message: {
@ -36,4 +41,11 @@ const props = defineProps({
color: inherit;
text-decoration: none;
}
.message-text {
display: inline-flex;
align-items: center;
white-space: pre-wrap;
word-break: break-word;
}
</style>

26
im-admin-ui/src/utils/emotion.ts

@ -0,0 +1,26 @@
const emoTextList = ['憨笑', '媚眼', '开心', '坏笑', '可怜', '爱心', '笑哭', '拍手', '惊喜', '打气',
'大哭', '流泪', '饥饿', '难受', '健身', '示爱', '色色', '眨眼', '暴怒', '惊恐',
'思考', '头晕', '大吐', '酷笑', '翻滚', '享受', '鼻涕', '快乐', '雀跃', '微笑',
'贪婪', '红心', '粉心', '星星', '大火', '眼睛', '音符', "叹号", "问号", "绿叶",
"燃烧", "喇叭", "警告", "信封", "房子", "礼物", "点赞", "举手", "喝彩", "点头",
"摇头", "偷瞄", "庆祝", "疾跑", "打滚", "惊吓", "起跳"
];
// 将匹配结果替换表情图片
let textToImg = (emoText) => {
let word = emoText.replace(/\#|\;/gi, '');
let idx = emoTextList.indexOf(word);
if (idx == -1) {
return emoText;
}
let url = new URL(`../assets/emoji/${idx}.gif`, import.meta.url).href;
return `<img src="${url}" style="width:20px;heigt:20px;" />`
}
export const transform = (content) => {
return content.replace(/\#[\u4E00-\u9FA5]{1,3}\;/gi, (text) => textToImg(text));
}

10
im-admin-ui/src/utils/str.ts

@ -0,0 +1,10 @@
export const html2Escape = (strText) => {
return strText.replace(/[<>&"]/g, function(c) {
return {
'<': '&lt;',
'>': '&gt;',
'&': '&amp;',
'"': '&quot;'
} [c];
});
}

11
im-admin-ui/src/utils/url.ts

@ -0,0 +1,11 @@
export const replaceURLWithHTMLLinks = (content) => {
// 使用正则表达式匹配更广泛的URL格式(此正则由deepseek生成)
const urlRegex = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|]|\bwww\.[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig;
return content.replace(urlRegex, (url) => {
// 如果URL不以http(s)://开头,则添加http://前缀
if (!url.startsWith("http")) {
url = "http://" + url;
}
return `<a href="${url}" target="_blank" style="color: #5359dc;text-decoration: underline;">${url}</a>`;
});
}

8
im-admin-ui/src/views/im/group/index.vue

@ -76,10 +76,10 @@
<image-preview :src="form.headImageThumb" :full-src="form.headImage" :width="50" :height="50" />
</el-form-item>
<el-form-item label="群名字" prop="name">
<el-input v-model="form.name" placeholder="请输入群名字" />
<el-input v-model="form.name" />
</el-form-item>
<el-form-item label="群主" prop="name">
<el-input v-model="form.ownerUserName" placeholder="请输入群名字" />
<el-input v-model="form.ownerUserName" />
</el-form-item>
<el-form-item label="创建时间" prop="createdTime">
<el-date-picker clearable v-model="form.createdTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss">
@ -92,9 +92,9 @@
<dict-tag :options="im_bool" :value="form.isBanned" />
</el-form-item>
<el-form-item v-if="form.isBanned" label="被封禁原因" prop="reason">
<el-input v-model="form.reason" placeholder="请输入被封禁原因" />
<el-input v-model="form.reason" />
</el-form-item>
<el-form-item label="群公告" prop="notice">
<el-form-item v-if="form.notice" label="群公告" prop="notice">
<el-input type="textarea" v-model="form.notice"></el-input>
</el-form-item>
</el-form>

16
im-admin-ui/src/views/im/user/index.vue

@ -70,36 +70,34 @@
<el-dialog :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
<el-form ref="userFormRef" :model="form" :rules="rules" label-width="100px" disabled>
<el-form-item label="用户头像" prop="headImage">
<image-preview v-if="scope.row.headImageThumb" :src="form.headImageThumb" :full-src="form.headImage"
<image-preview v-if="form.headImageThumb" :src="form.headImageThumb" :full-src="form.headImage"
:width="100" :height="100" />
</el-form-item>
<el-form-item label="用户名" prop="userName">
<el-input v-model="form.userName" placeholder="请输入用户名" />
<el-input v-model="form.userName" />
</el-form-item>
<el-form-item label="性别" prop="sex">
<dict-tag :options="sys_user_sex" :value="form.sex" />
</el-form-item>
<el-form-item label="用户昵称" prop="nickName">
<el-input v-model="form.nickName" placeholder="请输入用户昵称" />
<el-input v-model="form.nickName" />
</el-form-item>
<el-form-item label="个性签名" prop="signature">
<el-input v-model="form.signature" placeholder="请输入个性签名" />
<el-input v-model="form.signature" />
</el-form-item>
<el-form-item label="最后登录时间" prop="lastLoginTime">
<el-date-picker clearable v-model="form.lastLoginTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择最后登录时间">
<el-date-picker clearable v-model="form.lastLoginTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss">
</el-date-picker>
</el-form-item>
<el-form-item label="注册时间" prop="createdTime">
<el-date-picker clearable v-model="form.createdTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss"
placeholder="请选择创建时间">
<el-date-picker clearable v-model="form.createdTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss">
</el-date-picker>
</el-form-item>
<el-form-item label="是否被封禁" prop="isBanned">
<dict-tag :options="im_bool" :value="form.isBanned" />
</el-form-item>
<el-form-item v-if="form.isBanned" label="被封禁原因" prop="reason">
<el-input v-model="form.reason" placeholder="请输入被封禁原因" />
<el-input v-model="form.reason"/>
</el-form-item>
</el-form>
<template #footer>

547
im-admin-ui/src/views/index.vue

@ -1,81 +1,530 @@
<template>
<div class="app-container home">
<!-- 统计指标卡片 -->
<el-row :gutter="20" style="margin-bottom: 20px">
<el-col :span="6">
<el-card class="statistics-card">
<div class="statistics-item">
<div class="statistics-icon" style="background-color: #409EFF;">
<el-icon size="24">
<User />
</el-icon>
</div>
<div class="statistics-content">
<div class="statistics-title">总用户量</div>
<div class="statistics-value">{{ totalUserCount }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="statistics-card">
<div class="statistics-item">
<div class="statistics-icon" style="background-color: #67C23A;">
<el-icon size="24">
<ChatDotRound />
</el-icon>
</div>
<div class="statistics-content">
<div class="statistics-title">总群组数量</div>
<div class="statistics-value">{{ totalGroupCount }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="statistics-card">
<div class="statistics-item">
<div class="statistics-icon" style="background-color: #E6A23C;">
<el-icon size="24">
<Timer />
</el-icon>
</div>
<div class="statistics-content">
<div class="statistics-title">日活用户</div>
<div class="statistics-value">{{ dailyActiveCount }}</div>
</div>
</div>
</el-card>
</el-col>
<el-col :span="6">
<el-card class="statistics-card">
<div class="statistics-item">
<div class="statistics-icon" style="background-color: #909399;">
<el-icon size="24">
<Clock />
</el-icon>
</div>
<div class="statistics-content">
<div class="statistics-title">月活用户</div>
<div class="statistics-value">{{ monthlyActiveCount }}</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<!-- 统计图表 -->
<div class="chart-section">
<el-row :gutter="20">
<el-col :sm="24" :lg="12" style="padding-left: 20px">
<h2>盒子IM 后台管理系统</h2>
<h4>盒子IM是一个仿微信的聊天系统支持私聊群聊离线消息发送图片文件语音emoji表情音视频通话等功能</h4>
<el-col :span="12">
<el-card>
<template #header>
<span>用户注册</span>
<el-select v-model="registrationSelectedDays" @change="loadRegistrationChartData"
style="width: 100px; float: right;" size="small">
<el-option label="7天" :value="7"></el-option>
<el-option label="15天" :value="15"></el-option>
<el-option label="30天" :value="30"></el-option>
</el-select>
</template>
<div ref="registrationChartContainer" style="width: 100%; height: 300px;"></div>
</el-card>
</el-col>
<el-col :span="12">
<el-card>
<template #header>
<span>消息统计</span>
<el-select v-model="selectedDays" @change="loadChartData" style="width: 100px; float: right;"
size="small">
<el-option label="7天" :value="7"></el-option>
<el-option label="15天" :value="15"></el-option>
<el-option label="30天" :value="30"></el-option>
</el-select>
</template>
<div ref="chartContainer" style="width: 100%; height: 300px;"></div>
</el-card>
</el-col>
</el-row>
<el-divider />
</div>
</div>
</template>
<script setup name="Index" lang="ts">
const goTarget = (url: string) => {
window.open(url, '__blank');
import { ref, onMounted, nextTick, computed } from 'vue';
import * as echarts from 'echarts';
import { getDailyMessageCount } from '@/api/im/privateMessage';
import { getDailyGroupMessageCount } from '@/api/im/groupMessage';
import { getDailyRegistrationCount, getTotalUserCount, getActiveUserStats } from '@/api/im/user';
import { getTotalGroupCount } from '@/api/im/group';
import { User, ChatDotRound, Timer, Calendar, Clock } from '@element-plus/icons-vue';
const chartContainer = ref<HTMLElement>();
const selectedDays = ref(7);
let chartInstance: echarts.ECharts | null = null;
const registrationChartContainer = ref<HTMLElement>();
const registrationSelectedDays = ref(7);
let registrationChartInstance: echarts.ECharts | null = null;
//
const totalUserCount = ref(0);
//
const totalGroupCount = ref(0);
//
const dailyActiveCount = ref(0);
const weeklyActiveCount = ref(0);
const monthlyActiveCount = ref(0);
//
const loadTotalUserCount = async () => {
try {
const response = await getTotalUserCount();
totalUserCount.value = response.data;
} catch (error) {
console.error('加载总用户数量失败:', error);
totalUserCount.value = 0;
}
};
</script>
<style scoped lang="scss">
.home {
blockquote {
padding: 10px 20px;
margin: 0 0 20px;
font-size: 17.5px;
border-left: 5px solid #eee;
//
const loadTotalGroupCount = async () => {
try {
const response = await getTotalGroupCount();
totalGroupCount.value = response.data;
} catch (error) {
console.error('加载总群组数量失败:', error);
totalGroupCount.value = 0;
}
};
//
const loadActiveUserStats = async () => {
try {
const response = await getActiveUserStats();
dailyActiveCount.value = response.data.dailyActive;
weeklyActiveCount.value = response.data.weeklyActive;
monthlyActiveCount.value = response.data.monthlyActive;
} catch (error) {
console.error('加载活跃用户统计失败:', error);
dailyActiveCount.value = 0;
weeklyActiveCount.value = 0;
monthlyActiveCount.value = 0;
}
};
//
const loadChartData = async () => {
try {
//
const [privateResponse, groupResponse] = await Promise.all([
getDailyMessageCount(selectedDays.value),
getDailyGroupMessageCount(selectedDays.value)
]);
const privateData = privateResponse.data;
const groupData = groupResponse.data;
//
const chartData = processChartData(privateData, groupData);
//
if (chartInstance) {
chartInstance.setOption({
xAxis: {
data: chartData.dates
},
series: [
{
data: chartData.privateCounts
},
{
data: chartData.groupCounts
}
hr {
margin-top: 20px;
margin-bottom: 20px;
border: 0;
border-top: 1px solid #eee;
]
});
}
.col-item {
margin-bottom: 20px;
} catch (error) {
console.error('加载图表数据失败:', error);
}
};
//
const processChartData = (privateData: any[], groupData: any[]) => {
const dates: string[] = [];
const privateCounts: number[] = [];
const groupCounts: number[] = [];
ul {
padding: 0;
margin: 0;
//
const today = new Date();
for (let i = selectedDays.value - 1; i >= 0; i--) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
dates.push(dateStr);
//
const privateDayData = privateData.find(item => item.date === dateStr);
privateCounts.push(privateDayData ? parseInt(privateDayData.count) : 0);
//
const groupDayData = groupData.find(item => item.date === dateStr);
groupCounts.push(groupDayData ? parseInt(groupDayData.count) : 0);
}
font-family: 'open sans', 'Helvetica Neue', Helvetica, Arial, sans-serif;
font-size: 13px;
color: #676a6c;
overflow-x: hidden;
return { dates, privateCounts, groupCounts };
};
//
const loadRegistrationChartData = async () => {
try {
const response = await getDailyRegistrationCount(registrationSelectedDays.value);
const data = response.data;
//
const chartData = processRegistrationChartData(data);
//
if (registrationChartInstance) {
registrationChartInstance.setOption({
xAxis: {
data: chartData.dates
},
series: [{
data: chartData.counts
}]
});
}
} catch (error) {
console.error('加载用户注册统计图表数据失败:', error);
}
};
//
const processRegistrationChartData = (data: any[]) => {
const dates: string[] = [];
const counts: number[] = [];
//
const today = new Date();
for (let i = registrationSelectedDays.value - 1; i >= 0; i--) {
const date = new Date(today);
date.setDate(date.getDate() - i);
const dateStr = date.toISOString().split('T')[0];
dates.push(dateStr);
//
const dayData = data.find(item => item.date === dateStr);
counts.push(dayData ? parseInt(dayData.count) : 0);
}
return { dates, counts };
};
//
const initChart = async () => {
await nextTick();
if (chartContainer.value) {
chartInstance = echarts.init(chartContainer.value);
const option = {
title: {
text: '每日消息量趋势对比',
left: 'center'
},
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
let result = `${params[0].name}<br/>`;
params.forEach((param: any) => {
result += `${param.seriesName}: ${param.value}<br/>`;
});
return result;
}
},
legend: {
data: ['私聊消息', '群聊消息'],
top: 30
},
xAxis: {
type: 'category',
data: [],
axisLabel: {
formatter: (value: string) => {
return value.substring(5); // -
}
}
},
yAxis: {
type: 'value',
name: '消息量'
},
series: [
{
name: '私聊消息',
type: 'line',
data: [],
smooth: true,
itemStyle: {
color: '#409EFF'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: 'rgba(64, 158, 255, 0.3)'
}, {
offset: 1, color: 'rgba(64, 158, 255, 0.1)'
}]
}
}
},
{
name: '群聊消息',
type: 'line',
data: [],
smooth: true,
itemStyle: {
color: '#67C23A'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: 'rgba(103, 194, 58, 0.3)'
}, {
offset: 1, color: 'rgba(103, 194, 58, 0.1)'
}]
}
}
}
],
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '15%',
containLabel: true
}
};
chartInstance.setOption(option);
//
await loadChartData();
}
};
onMounted(() => {
loadTotalUserCount();
loadTotalGroupCount();
loadActiveUserStats();
initChart();
initRegistrationChart();
});
//
const initRegistrationChart = async () => {
await nextTick();
if (registrationChartContainer.value) {
registrationChartInstance = echarts.init(registrationChartContainer.value);
const option = {
title: {
text: '每日用户注册趋势',
left: 'center'
},
tooltip: {
trigger: 'axis',
formatter: (params: any) => {
const data = params[0];
return `${data.name}<br/>注册用户: ${data.value}`;
}
},
xAxis: {
type: 'category',
data: [],
axisLabel: {
formatter: (value: string) => {
return value.substring(5); // -
}
}
},
yAxis: {
type: 'value',
name: '注册用户数'
},
series: [{
name: '注册用户',
type: 'line',
data: [],
smooth: true,
itemStyle: {
color: '#E6A23C'
},
areaStyle: {
color: {
type: 'linear',
x: 0,
y: 0,
x2: 0,
y2: 1,
colorStops: [{
offset: 0, color: 'rgba(230, 162, 60, 0.3)'
}, {
offset: 1, color: 'rgba(230, 162, 60, 0.1)'
}]
}
}
}],
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
}
};
ul {
list-style-type: none;
registrationChartInstance.setOption(option);
//
await loadRegistrationChartData();
}
};
</script>
<style scoped lang="scss">
.app-container {
padding: 20px;
}
h4 {
margin-top: 0px;
//
.statistics-card {
border-radius: 8px;
border: 1px solid #f0f2f5;
:deep(.el-card__body) {
padding: 16px;
}
}
h2 {
margin-top: 10px;
font-size: 26px;
font-weight: 100;
.statistics-item {
display: flex;
align-items: center;
.statistics-icon {
width: 48px;
height: 48px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
color: white;
}
p {
margin-top: 10px;
.statistics-content {
flex: 1;
b {
font-weight: 700;
.statistics-title {
font-size: 13px;
color: #8c8c8c;
margin-bottom: 4px;
}
.statistics-value {
font-size: 24px;
font-weight: 600;
color: #262626;
line-height: 1;
}
}
}
.update-log {
ol {
display: block;
list-style-type: decimal;
margin-block-start: 1em;
margin-block-end: 1em;
margin-inline-start: 0;
margin-inline-end: 0;
padding-inline-start: 40px;
//
.chart-section {
margin-top: 24px;
.el-card {
border-radius: 8px;
border: 1px solid #f0f2f5;
:deep(.el-card__body) {
padding: 16px;
}
}
}
//
:deep(.el-card__header) {
border-bottom: 1px solid #f0f2f5;
padding: 12px 16px;
span {
font-size: 14px;
font-weight: 500;
color: #262626;
}
}
</style>

6
im-admin/pom.xml

@ -117,6 +117,12 @@
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>${springdoc.version}</version>
</dependency>
<dependency>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc</artifactId>

30
im-admin/ruoyi-admin/src/main/resources/application.yml

@ -113,7 +113,10 @@ security:
# swagger 文档配置
- /*/api-docs
- /*/api-docs/**
- /swagger-ui/**
- /swagger-ui.html
- /v3/api-docs
- /v3/api-docs/**
# MyBatisPlus配置
# https://baomidou.com/config/
@ -144,16 +147,29 @@ mybatis-encryptor:
publicKey:
privateKey:
# 【bug】ruoyi的这个文档配置似乎没有生效,跟代码也对不上
# SpringDoc OpenAPI 3.0 配置,访问: http://localhost:8889/swagger-ui/index.html
springdoc:
api-docs:
# 是否开启接口文档
enabled: true
# API文档路径
path: /v3/api-docs
swagger-ui:
# Swagger UI 访问路径
path: /swagger-ui.html
# 是否启用
enabled: true
# 显示操作ID
display-operation-id: true
# 显示请求持续时间
display-request-duration: true
info:
# 标题
title: '标题:${app.name}后台管理系统'
title: '${app.name}后台管理系统'
# 版本
version: '版本号: ${app.version}'
version: '${app.version}'
# 描述
description: '盒子IM后台管理系统API文档'
components:
# 鉴权方式配置
security-schemes:
@ -161,10 +177,10 @@ springdoc:
type: APIKEY
in: HEADER
name: ${sa-token.token-name}
#这里定义了两个分组,可定义多个,也可以不定义
# 分组配置
group-configs:
- group: 1.演示模块
packages-to-scan: org.dromara.demo
- group: 1.IM模块
packages-to-scan: org.dromara.im
- group: 2.通用模块
packages-to-scan: org.dromara.web
- group: 3.系统模块

5
im-admin/ruoyi-common/ruoyi-common-doc/pom.xml

@ -26,6 +26,11 @@
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<dependency>
<groupId>com.github.therapi</groupId>
<artifactId>therapi-runtime-javadoc</artifactId>

9
im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImGroupController.java

@ -104,4 +104,13 @@ public class ImGroupController extends BaseController {
public TableDataInfo<ImGroupMemberVo> findMembers( ImGroupMemberBo bo, PageQuery pageQuery) {
return groupMemberService.queryPageList(bo,pageQuery);
}
/**
* 获取总群组数量
*/
@SaCheckPermission("im:group:list")
@GetMapping("/totalCount")
public R<Long> getTotalGroupCount() {
return R.ok(groupService.getTotalGroupCount());
}
}

18
im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImGroupMessageController.java

@ -11,10 +11,10 @@ import org.dromara.im.domain.bo.ImGroupMessageBo;
import org.dromara.im.domain.vo.ImGroupMessageVo;
import org.dromara.im.service.IImGroupMessageService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 群消息
@ -51,5 +51,15 @@ public class ImGroupMessageController extends BaseController {
return R.ok(imGroupMessageService.queryById(id));
}
/**
* 按天统计群聊消息量
*
* @param days 统计天数默认7天
*/
@SaCheckPermission("im:groupMessage:list")
@GetMapping("/dailyCount")
public R<List<Map<String, Object>>> getDailyGroupMessageCount(@RequestParam(defaultValue = "7") Integer days) {
return R.ok(imGroupMessageService.getDailyGroupMessageCount(days));
}
}

19
im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImPrivateMessageController.java

@ -11,10 +11,10 @@ import org.dromara.im.domain.bo.ImPrivateMessageBo;
import org.dromara.im.domain.vo.ImPrivateMessageVo;
import org.dromara.im.service.IImPrivateMessageService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
/**
* 私聊消息
@ -52,4 +52,15 @@ public class ImPrivateMessageController extends BaseController {
return R.ok(imPrivateMessageService.queryById(id));
}
/**
* 按天统计私聊消息量
*
* @param days 统计天数默认7天
*/
@SaCheckPermission("im:privateMessage:list")
@GetMapping("/dailyCount")
public R<List<Map<String, Object>>> getDailyMessageCount(@RequestParam(value = "days", defaultValue = "7") Integer days) {
return R.ok(imPrivateMessageService.getDailyMessageCount(days));
}
}

35
im-admin/ruoyi-im/src/main/java/org/dromara/im/controller/ImUserController.java

@ -22,7 +22,9 @@ import org.dromara.im.util.CommaTextUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -101,4 +103,37 @@ public class ImUserController extends BaseController {
List<ImUserVo> vos = userService.findByIds(arrIds);
return R.ok(vos);
}
/**
* 按天统计用户注册数量
*
* @param days 统计天数默认7天
*/
@SaCheckPermission("im:user:list")
@GetMapping("/dailyRegistrationCount")
public R<List<Map<String, Object>>> getDailyRegistrationCount(@RequestParam(value = "days", defaultValue = "7") Integer days) {
return R.ok(userService.getDailyRegistrationCount(days));
}
/**
* 获取总用户数量
*/
@SaCheckPermission("im:user:list")
@GetMapping("/totalCount")
public R<Long> getTotalUserCount() {
return R.ok(userService.getTotalUserCount());
}
/**
* 获取活跃用户统计日活周活月活
*/
@SaCheckPermission("im:user:list")
@GetMapping("/activeStats")
public R<Map<String, Long>> getActiveUserStats() {
Map<String, Long> stats = new HashMap<>();
stats.put("dailyActive", userService.getDailyActiveUserCount());
stats.put("weeklyActive", userService.getWeeklyActiveUserCount());
stats.put("monthlyActive", userService.getMonthlyActiveUserCount());
return R.ok(stats);
}
}

16
im-admin/ruoyi-im/src/main/java/org/dromara/im/mapper/ImGroupMessageMapper.java

@ -1,9 +1,14 @@
package org.dromara.im.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.im.domain.ImGroupMessage;
import org.dromara.im.domain.vo.ImGroupMessageVo;
import java.util.List;
import java.util.Map;
/**
* 群消息Mapper接口
*
@ -12,4 +17,15 @@ import org.dromara.im.domain.vo.ImGroupMessageVo;
*/
public interface ImGroupMessageMapper extends BaseMapperPlus<ImGroupMessage, ImGroupMessageVo> {
/**
* 按天统计群聊消息量
* @param days 统计天数
* @return 统计结果
*/
@Select("SELECT DATE(send_time) as date, COUNT(*) as count " +
"FROM im_group_message " +
"WHERE send_time >= DATE_SUB(CURDATE(), INTERVAL #{days} DAY) " +
"GROUP BY DATE(send_time) " +
"ORDER BY date ASC")
List<Map<String, Object>> getDailyGroupMessageCount(@Param("days") Integer days);
}

16
im-admin/ruoyi-im/src/main/java/org/dromara/im/mapper/ImPrivateMessageMapper.java

@ -1,9 +1,14 @@
package org.dromara.im.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.im.domain.ImPrivateMessage;
import org.dromara.im.domain.vo.ImPrivateMessageVo;
import java.util.List;
import java.util.Map;
/**
* 私聊消息Mapper接口
*
@ -12,4 +17,15 @@ import org.dromara.im.domain.vo.ImPrivateMessageVo;
*/
public interface ImPrivateMessageMapper extends BaseMapperPlus<ImPrivateMessage, ImPrivateMessageVo> {
/**
* 按天统计私聊消息量
* @param days 统计天数
* @return 统计结果
*/
@Select("SELECT DATE(send_time) as date, COUNT(*) as count " +
"FROM im_private_message " +
"WHERE send_time >= DATE_SUB(CURDATE(), INTERVAL #{days} DAY) " +
"GROUP BY DATE(send_time) " +
"ORDER BY date ASC")
List<Map<String, Object>> getDailyMessageCount(@Param("days") Integer days);
}

17
im-admin/ruoyi-im/src/main/java/org/dromara/im/mapper/ImUserMapper.java

@ -1,9 +1,14 @@
package org.dromara.im.mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.dromara.common.mybatis.core.mapper.BaseMapperPlus;
import org.dromara.im.domain.ImUser;
import org.dromara.im.domain.vo.ImUserVo;
import java.util.List;
import java.util.Map;
/**
* 用户Mapper接口
*
@ -12,4 +17,16 @@ import org.dromara.im.domain.vo.ImUserVo;
*/
public interface ImUserMapper extends BaseMapperPlus<ImUser, ImUserVo> {
/**
* 按天统计用户注册数量
* @param days 统计天数
* @return 统计结果
*/
@Select("SELECT DATE(created_time) as date, COUNT(*) as count " +
"FROM im_user " +
"WHERE created_time >= DATE_SUB(CURDATE(), INTERVAL #{days} DAY) " +
"GROUP BY DATE(created_time) " +
"ORDER BY date ASC")
List<Map<String, Object>> getDailyRegistrationCount(@Param("days") Integer days);
}

11
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImGroupMessageService.java

@ -5,6 +5,9 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.im.domain.bo.ImGroupMessageBo;
import org.dromara.im.domain.vo.ImGroupMessageVo;
import java.util.List;
import java.util.Map;
/**
* 群消息Service接口
*
@ -30,6 +33,12 @@ public interface IImGroupMessageService {
*/
TableDataInfo<ImGroupMessageVo> queryPageList(ImGroupMessageBo bo, PageQuery pageQuery);
/**
* 按天统计群聊消息数量
*
* @param days 统计天数
* @return 按天统计数据
*/
List<Map<String, Object>> getDailyGroupMessageCount(Integer days);
}

5
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImGroupService.java

@ -65,6 +65,11 @@ public interface IImGroupService {
List<ImGroupVo> findByName(String name);
/**
* 获取总群组数量
* @return 总群组数量
*/
Long getTotalGroupCount();
}

11
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImPrivateMessageService.java

@ -5,6 +5,9 @@ import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.im.domain.bo.ImPrivateMessageBo;
import org.dromara.im.domain.vo.ImPrivateMessageVo;
import java.util.List;
import java.util.Map;
/**
* 私聊消息Service接口
*
@ -31,6 +34,12 @@ public interface IImPrivateMessageService {
TableDataInfo<ImPrivateMessageVo> queryPageList(ImPrivateMessageBo bo, PageQuery pageQuery);
/**
* 按天统计私聊消息量
*
* @param days 统计天数
* @return 统计结果
*/
List<Map<String, Object>> getDailyMessageCount(Integer days);
}

33
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/IImUserService.java

@ -8,6 +8,7 @@ import org.dromara.im.domain.dto.ImUserUnbanDto;
import org.dromara.im.domain.vo.ImUserVo;
import java.util.List;
import java.util.Map;
/**
* 用户Service接口
@ -70,4 +71,36 @@ public interface IImUserService {
*/
List<ImUserVo> findByIds(List<Long> ids);
/**
* 按天统计用户注册数量
*
* @param days 统计天数
* @return 统计结果
*/
List<Map<String, Object>> getDailyRegistrationCount(Integer days);
/**
* 获取总用户数量
* @return 总用户数量
*/
Long getTotalUserCount();
/**
* 获取日活跃用户数量最近1天
* @return 日活跃用户数量
*/
Long getDailyActiveUserCount();
/**
* 获取周活跃用户数量最近7天
* @return 周活跃用户数量
*/
Long getWeeklyActiveUserCount();
/**
* 获取月活跃用户数量最近30天
* @return 月活跃用户数量
*/
Long getMonthlyActiveUserCount();
}

14
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImGroupMessageServiceImpl.java

@ -16,6 +16,7 @@ import org.dromara.im.mapper.ImGroupMessageMapper;
import org.dromara.im.service.IImGroupMessageService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
@ -56,6 +57,19 @@ public class ImGroupMessageServiceImpl implements IImGroupMessageService {
return TableDataInfo.build(result);
}
/**
* 按天统计群聊消息数量
*
* @param days 统计天数
* @return 按天统计数据
*/
@Override
public List<Map<String, Object>> getDailyGroupMessageCount(Integer days) {
if (days == null || days <= 0) {
days = 7; // 默认统计最近7天
}
return baseMapper.getDailyGroupMessageCount(days);
}
private LambdaQueryWrapper<ImGroupMessage> buildQueryWrapper(ImGroupMessageBo bo) {
Map<String, Object> params = bo.getParams();

9
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImGroupServiceImpl.java

@ -128,6 +128,15 @@ public class ImGroupServiceImpl implements IImGroupService {
return baseMapper.selectVoList(queryWrapper);
}
/**
* 获取总群组数量
* @return 总群组数量
*/
@Override
public Long getTotalGroupCount() {
return baseMapper.selectCount(null);
}
private LambdaQueryWrapper<ImGroup> buildQueryWrapper(ImGroupBo bo) {
Map<String, Object> params = bo.getParams();
LambdaQueryWrapper<ImGroup> wrapper = Wrappers.lambdaQuery();

14
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImPrivateMessageServiceImpl.java

@ -16,6 +16,7 @@ import org.dromara.im.mapper.ImPrivateMessageMapper;
import org.dromara.im.service.IImPrivateMessageService;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@ -57,6 +58,19 @@ public class ImPrivateMessageServiceImpl implements IImPrivateMessageService {
return TableDataInfo.build(result);
}
/**
* 按天统计私聊消息量
*
* @param days 统计天数
* @return 统计结果
*/
@Override
public List<Map<String, Object>> getDailyMessageCount(Integer days) {
if (days == null || days <= 0) {
days = 7; // 默认统计最近7天
}
return baseMapper.getDailyMessageCount(days);
}
private LambdaQueryWrapper<ImPrivateMessage> buildQueryWrapper(ImPrivateMessageBo bo) {
Map<String, Object> params = bo.getParams();

64
im-admin/ruoyi-im/src/main/java/org/dromara/im/service/impl/ImUserServiceImpl.java

@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.logging.log4j.util.Strings;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.PageQuery;
@ -23,7 +24,9 @@ import org.dromara.im.mq.ImRedisMQTemplate;
import org.dromara.im.service.IImUserService;
import org.springframework.stereotype.Service;
import java.util.Date;
import java.util.List;
import java.util.Map;
/**
* 用户Service业务层处理
@ -125,4 +128,65 @@ public class ImUserServiceImpl implements IImUserService {
queryWrapper.in(ImUser::getId, ids);
return baseMapper.selectVoList(queryWrapper);
}
/**
* 按天统计用户注册数量
*
* @param days 统计天数
* @return 统计结果
*/
@Override
public List<Map<String, Object>> getDailyRegistrationCount(Integer days) {
if (days == null || days <= 0) {
days = 7; // 默认统计最近7天
}
return baseMapper.getDailyRegistrationCount(days);
}
/**
* 获取总用户数量
*
* @return 总用户数量
*/
@Override
public Long getTotalUserCount() {
return baseMapper.selectCount(null);
}
/**
* 获取日活跃用户数量最近1天
*
* @return 日活跃用户数量
*/
@Override
public Long getDailyActiveUserCount() {
LambdaQueryWrapper<ImUser> wrapper = Wrappers.lambdaQuery();
wrapper.ge(ImUser::getLastLoginTime, DateUtils.addMonths(new Date(), -1));
return baseMapper.selectCount(wrapper);
}
/**
* 获取周活跃用户数量最近7天
*
* @return 周活跃用户数量
*/
@Override
public Long getWeeklyActiveUserCount() {
LambdaQueryWrapper<ImUser> wrapper = Wrappers.lambdaQuery();
wrapper.ge(ImUser::getLastLoginTime, DateUtils.addMonths(new Date(), -7));
return baseMapper.selectCount(wrapper);
}
/**
* 获取月活跃用户数量最近30天
*
* @return 月活跃用户数量
*/
@Override
public Long getMonthlyActiveUserCount() {
LambdaQueryWrapper<ImUser> wrapper = Wrappers.lambdaQuery();
wrapper.ge(ImUser::getLastLoginTime, DateUtils.addMonths(new Date(), -30));
return baseMapper.selectCount(wrapper);
}
}

Loading…
Cancel
Save