diff --git a/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/controller/notice/VersionController.java b/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/controller/notice/VersionController.java new file mode 100644 index 0000000..489ae81 --- /dev/null +++ b/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/controller/notice/VersionController.java @@ -0,0 +1,85 @@ +package org.nl.system.controller.notice; + +import cn.dev33.satoken.annotation.SaCheckPermission; +import cn.dev33.satoken.annotation.SaIgnore; +import com.alibaba.fastjson.JSONObject; +import lombok.extern.slf4j.Slf4j; +import org.nl.common.domain.query.PageQuery; +import org.nl.system.service.notice.IVersionService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + * 版本更新通知控制器 + * + * @author miguannan + * @date 2026-05-06 + */ +@Slf4j +@RestController +@RequestMapping("/api/version") +public class VersionController { + + @Autowired + private IVersionService versionService; + + /** + * 发布版本更新通知 + * + * @param json 版本信息 + * @return ResponseEntity + */ + @PostMapping("/release") + public ResponseEntity release(@RequestBody JSONObject json) { + versionService.releaseVersion(json); + return new ResponseEntity<>(HttpStatus.OK); + } + + /** + * 获取当前版本信息(供前端轮询) + * + * @return 版本信息 + */ + @GetMapping("/current") + @SaIgnore + public ResponseEntity current() { + return new ResponseEntity<>(versionService.getCurrentVersion(), HttpStatus.OK); + } + + /** + * 查询版本通知列表 + * + * @param pageQuery 分页参数 + * @return 版本通知分页数据 + */ + @GetMapping("/list") + public ResponseEntity list(PageQuery pageQuery) { + return new ResponseEntity<>(versionService.listVersions(pageQuery), HttpStatus.OK); + } + + /** + * 修改版本通知 + * + * @param json 通知信息(id, title, content) + * @return ResponseEntity + */ + @PutMapping("/update") + public ResponseEntity update(@RequestBody JSONObject json) { + versionService.updateVersion(json); + return new ResponseEntity<>(HttpStatus.OK); + } + + /** + * 删除版本通知 + * + * @param id 通知ID + * @return ResponseEntity + */ + @DeleteMapping("/delete/{id}") + public ResponseEntity delete(@PathVariable String id) { + versionService.deleteVersion(id); + return new ResponseEntity<>(HttpStatus.OK); + } +} diff --git a/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/service/notice/IVersionService.java b/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/service/notice/IVersionService.java new file mode 100644 index 0000000..7e888cd --- /dev/null +++ b/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/service/notice/IVersionService.java @@ -0,0 +1,54 @@ +package org.nl.system.service.notice; + +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.service.IService; +import org.nl.common.domain.query.PageQuery; +import org.nl.system.service.notice.dao.SysNotice; + +import java.util.Map; + +/** + * 版本更新通知服务接口 + * + * @author miguannan + * @date 2026-05-06 + */ +public interface IVersionService extends IService { + + /** + * 发布版本更新通知 + * + * @param json 版本信息(version, title, content) + */ + void releaseVersion(JSONObject json); + + /** + * 获取当前版本信息 + * + * @return 包含 version、enabled、pollInterval、releaseTime 的 Map + */ + Map getCurrentVersion(); + + /** + * 查询版本通知列表 + * + * @param pageQuery 分页参数 + * @return 版本通知分页数据 + */ + IPage listVersions(PageQuery pageQuery); + + /** + * 修改版本通知 + * + * @param json 通知信息(id, title, content) + */ + void updateVersion(JSONObject json); + + /** + * 删除版本通知 + * + * @param id 通知ID + */ + void deleteVersion(String id); +} diff --git a/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/service/notice/impl/VersionServiceImpl.java b/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/service/notice/impl/VersionServiceImpl.java new file mode 100644 index 0000000..a5bdb89 --- /dev/null +++ b/wms/nladmin-system/nlsso-server/src/main/java/org/nl/system/service/notice/impl/VersionServiceImpl.java @@ -0,0 +1,187 @@ +package org.nl.system.service.notice.impl; + + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSONObject; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import lombok.extern.slf4j.Slf4j; +import org.nl.common.domain.query.PageQuery; +import org.nl.common.exception.BadRequestException; +import org.nl.common.mnt.websocket.MsgType; +import org.nl.common.mnt.websocket.SocketMsg; +import org.nl.common.mnt.websocket.WebSocketServer; +import org.nl.system.service.notice.ISysNoticeService; +import org.nl.system.service.notice.IVersionService; +import org.nl.system.service.notice.dao.SysNotice; +import org.nl.system.service.notice.dao.mapper.SysNoticeMapper; +import org.nl.system.service.param.ISysParamService; +import org.nl.system.service.param.dao.Param; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +/** + * 版本更新通知服务实现 + * + * @author miguannan + * @date 2026-05-06 + */ +@Slf4j +@Service +public class VersionServiceImpl extends ServiceImpl implements IVersionService { + + @Autowired + private ISysNoticeService noticeService; + + @Autowired + private ISysParamService paramService; + + @Autowired + private SysNoticeMapper noticeMapper; + + @Override + @Transactional(rollbackFor = Exception.class) + public void releaseVersion(JSONObject json) { + String version = json.getString("version"); + String title = json.getString("title"); + String content = json.getString("content"); + + if (StrUtil.isBlank(version)) { + throw new BadRequestException("版本号不能为空"); + } + if (StrUtil.isBlank(title)) { + throw new BadRequestException("标题不能为空"); + } + + // 检查是否已有相同版本的未读通知,避免重复发布 + long count = noticeMapper.selectCount(new LambdaQueryWrapper() + .eq(SysNotice::getNotice_type, "version_update") + .likeRight(SysNotice::getNotice_title, version + " ")); + if (count > 0) { + throw new BadRequestException("已存在相同版本发布信息,请先处理后再发布"); + } + + // 1. 创建通知记录 + noticeService.createNotice(content, version + " " + title, "version_update"); + + // 2. 更新系统版本号 + Param appVersionParam = paramService.findByCode("app_version"); + if (appVersionParam == null) { + throw new BadRequestException("系统参数 app_version 不存在,请先执行初始化脚本"); + } + appVersionParam.setValue(version); + paramService.update(appVersionParam); + + // 3. WebSocket 广播给所有在线用户 + JSONObject msgData = new JSONObject(); + msgData.put("data", "version_update"); + msgData.put("version", version); + msgData.put("title", title); + SocketMsg socketMsg = new SocketMsg(msgData, MsgType.INFO); + try { + WebSocketServer.sendInfo(socketMsg, null); + } catch (IOException e) { + log.error("版本更新WebSocket广播失败", e); + } + } + + @Override + public Map getCurrentVersion() { + Map result = new HashMap<>(); + + // 当前版本号 + Param appVersionParam = paramService.findByCode("app_version"); + String version = (appVersionParam != null) ? appVersionParam.getValue() : "1.0.0"; + + // 功能开关 + Param enabledParam = paramService.findByCode("version_notify_enabled"); + boolean enabled = (enabledParam == null) || "true".equals(enabledParam.getValue()); + + // 轮询间隔 + Param intervalParam = paramService.findByCode("version_notify_poll_interval"); + int pollInterval = 30; + if (intervalParam != null && StrUtil.isNotBlank(intervalParam.getValue())) { + try { + pollInterval = Integer.parseInt(intervalParam.getValue()); + } catch (NumberFormatException e) { + log.warn("version_notify_poll_interval 值非法: {}", intervalParam.getValue()); + } + } + + result.put("version", version); + result.put("enabled", enabled); + result.put("pollInterval", pollInterval); + + // 最新版本通知的发布时间 + SysNotice latest = noticeMapper.selectOne(new QueryWrapper() + .eq("notice_type", "version_update") + .orderByDesc("create_time") + .last("LIMIT 1")); + if (latest != null) { + result.put("releaseTime", latest.getCreate_time()); + // notice_title 存储格式为 "version title",分离出版本号和标题 + String noticeTitle = latest.getNotice_title(); + if (StrUtil.isNotBlank(noticeTitle) && noticeTitle.startsWith(version + " ")) { + result.put("title", noticeTitle.substring(version.length() + 1)); + } else { + result.put("title", noticeTitle); + } + result.put("content", latest.getNotice_content()); + } + + return result; + } + + @Override + public IPage listVersions(PageQuery pageQuery) { + LambdaQueryWrapper lam = new LambdaQueryWrapper<>(); + lam.eq(SysNotice::getNotice_type, "version_update") + .orderByDesc(SysNotice::getCreate_time); + IPage pages = new Page<>(pageQuery.getPage() + 1, pageQuery.getSize()); + noticeMapper.selectPage(pages, lam); + return pages; + } + + @Override + public void updateVersion(JSONObject json) { + String id = json.getString("id"); + String title = json.getString("title"); + String content = json.getString("content"); + + if (StrUtil.isBlank(id)) { + throw new BadRequestException("ID不能为空"); + } + if (StrUtil.isBlank(title)) { + throw new BadRequestException("标题不能为空"); + } + + SysNotice notice = noticeMapper.selectById(id); + if (notice == null || !"version_update".equals(notice.getNotice_type())) { + throw new BadRequestException("版本通知不存在"); + } + + notice.setNotice_title(title); + notice.setNotice_content(content); + noticeMapper.updateById(notice); + } + + @Override + public void deleteVersion(String id) { + if (StrUtil.isBlank(id)) { + throw new BadRequestException("ID不能为空"); + } + SysNotice notice = noticeMapper.selectById(id); + if (notice == null || !"version_update".equals(notice.getNotice_type())) { + throw new BadRequestException("版本通知不存在"); + } + noticeMapper.deleteById(id); + } +} diff --git a/wms/nladmin-ui/src/App.vue b/wms/nladmin-ui/src/App.vue index 5838149..5a4fd3d 100644 --- a/wms/nladmin-ui/src/App.vue +++ b/wms/nladmin-ui/src/App.vue @@ -1,17 +1,49 @@ + diff --git a/wms/nladmin-ui/src/api/system/version.js b/wms/nladmin-ui/src/api/system/version.js new file mode 100644 index 0000000..92d02cd --- /dev/null +++ b/wms/nladmin-ui/src/api/system/version.js @@ -0,0 +1,64 @@ +import request from '@/utils/request' + +/** + * 获取当前版本信息 + * @returns {AxiosPromise} + */ +export function getCurrentVersion() { + return request({ + url: '/api/version/current', + method: 'get' + }) +} + +/** + * 发布版本更新通知 + * @param {Object} data - { version, title, content } + * @returns {AxiosPromise} + */ +export function releaseVersion(data) { + return request({ + url: '/api/version/release', + method: 'post', + data + }) +} + +/** + * 查询版本通知列表 + * @returns {AxiosPromise} + */ +export function listVersions(params) { + return request({ + url: '/api/version/list', + method: 'get', + params + }) +} + +/** + * 修改版本通知 + * @param {Object} data - { id, title, content } + * @returns {AxiosPromise} + */ +export function updateVersion(data) { + return request({ + url: '/api/version/update', + method: 'put', + data + }) +} + +/** + * 删除版本通知 + * @param {string} id + * @returns {AxiosPromise} + */ +export function deleteVersion(id) { + return request({ + url: '/api/version/delete/' + id, + method: 'delete' + }) +} + +export default { getCurrentVersion, releaseVersion, listVersions, updateVersion, deleteVersion } diff --git a/wms/nladmin-ui/src/components/VersionNotification/VersionNotification.vue b/wms/nladmin-ui/src/components/VersionNotification/VersionNotification.vue new file mode 100644 index 0000000..fec9f33 --- /dev/null +++ b/wms/nladmin-ui/src/components/VersionNotification/VersionNotification.vue @@ -0,0 +1,120 @@ + + + + + diff --git a/wms/nladmin-ui/src/i18n/langs/en.js b/wms/nladmin-ui/src/i18n/langs/en.js index 783672e..c768f5e 100644 --- a/wms/nladmin-ui/src/i18n/langs/en.js +++ b/wms/nladmin-ui/src/i18n/langs/en.js @@ -130,5 +130,21 @@ export default { 'disk': 'Disk Utilization', 'cpu_monitoring': 'Cpu Utilization Monitoring', 'memory_monitoring': 'Memory Utilization Monitoring' + }, + 'version': { + 'title': 'System Version Update', + 'versionNo': 'Version', + 'releaseTime': 'Release Time', + 'content': 'Update Content', + 'confirm': 'I Know', + 'releaseTitle': 'Publish Version Notice', + 'noticeTitle': 'Notice Title', + 'release': 'Publish', + 'currentInfo': 'Current Version Info', + 'switch': 'Notification Switch', + 'pollInterval': 'Polling Interval', + 'enabled': 'Enabled', + 'disabled': 'Disabled', + 'seconds': 's' } } diff --git a/wms/nladmin-ui/src/i18n/langs/in.js b/wms/nladmin-ui/src/i18n/langs/in.js index 122b11c..074e76c 100644 --- a/wms/nladmin-ui/src/i18n/langs/in.js +++ b/wms/nladmin-ui/src/i18n/langs/in.js @@ -130,5 +130,21 @@ export default { 'disk': 'Kadar penggunaan disk', 'cpu_monitoring': 'Monitor penggunaan CPU', 'memory_monitoring': 'Monitor penggunaan memori' + }, + 'version': { + 'title': 'Update Versi Sistem', + 'versionNo': 'Versi', + 'releaseTime': 'Waktu Rilis', + 'content': 'Konten Update', + 'confirm': 'Saya Mengerti', + 'releaseTitle': 'Terbitkan Notifikasi Versi', + 'noticeTitle': 'Judul Notifikasi', + 'release': 'Terbitkan', + 'currentInfo': 'Info Versi Saat Ini', + 'switch': 'Sakelar Notifikasi', + 'pollInterval': 'Interval Polling', + 'enabled': 'Diaktifkan', + 'disabled': 'Dinonaktifkan', + 'seconds': 'detik' } } diff --git a/wms/nladmin-ui/src/i18n/langs/zh-CN.js b/wms/nladmin-ui/src/i18n/langs/zh-CN.js index 5be8d66..09f098c 100644 --- a/wms/nladmin-ui/src/i18n/langs/zh-CN.js +++ b/wms/nladmin-ui/src/i18n/langs/zh-CN.js @@ -130,5 +130,21 @@ export default { 'disk': '磁盘使用率', 'cpu_monitoring': 'CPU使用率监控', 'memory_monitoring': '内存使用率监控' + }, + 'version': { + 'title': '系统版本更新', + 'versionNo': '版本号', + 'releaseTime': '发布时间', + 'content': '更新内容', + 'confirm': '我知道了', + 'releaseTitle': '发布版本通知', + 'noticeTitle': '通知标题', + 'release': '发布', + 'currentInfo': '当前版本信息', + 'switch': '通知开关', + 'pollInterval': '轮询间隔', + 'enabled': '已启用', + 'disabled': '已禁用', + 'seconds': '秒' } } diff --git a/wms/nladmin-ui/src/layout/components/Navbar.vue b/wms/nladmin-ui/src/layout/components/Navbar.vue index 4c8e4e8..4f3ec5e 100644 --- a/wms/nladmin-ui/src/layout/components/Navbar.vue +++ b/wms/nladmin-ui/src/layout/components/Navbar.vue @@ -12,7 +12,7 @@ --> - + @@ -31,23 +31,23 @@ - {{ $t('common.Layout_setting') }} + {{ $t('auto.common.Layout_setting') }} - {{ $t('common.Personal_center') }} + {{ $t('auto.common.Personal_center') }} - {{ $t('common.Log_out') }} + {{ $t('auto.common.Log_out') }} - - + + {{ language }} @@ -66,9 +66,7 @@ import Breadcrumb from '@/components/Breadcrumb' import Hamburger from '@/components/Hamburger' import TopNav from '@/components/TopNav' -import Doc from '@/components/Doc' import Screenfull from '@/components/Screenfull' -import SizeSelect from '@/components/SizeSelect' import Search from '@/components/HeaderSearch' import Avatar from '@/assets/images/avatar.png' import NoticeIcon from '@/views/system/notice/NoticeIcon.vue' @@ -81,9 +79,7 @@ export default { Breadcrumb, Hamburger, Screenfull, - SizeSelect, Search, - Doc, TopNav }, data() { @@ -93,9 +89,6 @@ export default { language: '简体中文' } }, - created() { - this.initLang() - }, computed: { ...mapGetters([ 'sidebar', @@ -120,16 +113,11 @@ export default { } } }, + created() { + this.setLang(localStorage.getItem('lang')) + this.initWebSocket() + }, methods: { - initLang() { - // 初始化语言 - let item = localStorage.getItem('lang') - if (item === null) { - item = 'zh' - } - localStorage.setItem('lang', item) - this.setLang(item) - }, // 中英文切换 langChange(command) { this.$i18n.locale = command @@ -150,9 +138,9 @@ export default { this.$store.dispatch('app/toggleSideBar') }, open() { - this.$confirm(this.$t('common.Tip13'), this.$t('common.Tips'), { - confirmButtonText: this.$t('common.Confirm'), - cancelButtonText: this.$t('common.Cancel'), + this.$confirm(this.$t('auto.common.Tip13'), this.$t('auto.common.Tips'), { + confirmButtonText: this.$t('auto.common.Confirm'), + cancelButtonText: this.$t('auto.common.Cancel'), type: 'warning' }).then(() => { this.logout() @@ -162,6 +150,38 @@ export default { this.$store.dispatch('LogOut').then(() => { location.reload() }) + }, + initWebSocket() { + // const wsUri = (process.env.VUE_APP_WS_API === '/' ? '/' : (process.env.VUE_APP_WS_API + '/')) + 'messageInfo' + const wsUri = window.g.prod.VUE_APP_BASE_API.replace('http', 'ws') + '/webSocket/' + 'messageInfo' + this.websock = new WebSocket(wsUri) + this.websock.onerror = this.webSocketOnError + this.websock.onmessage = this.webSocketOnMessage + }, + webSocketOnError(e) { + this.$notify({ + title: this.$t('auto.common.Tip14'), + type: 'error', + duration: 0 + }) + }, + webSocketOnMessage(e) { + const data = JSON.parse(e.data) + if (data.msgType === 'INFO') { + if (data.msg.data === 'notice_message_update') { + this.$bus.emit(data.msg.data, data.msg.msgType) + } else if (data.msg.data === 'version_update') { + this.$bus.emit('version_update', data.msg) + } + } else if (data.msgType === 'ERROR') { + this.$notify({ + title: '', + message: data.msg, + dangerouslyUseHTMLString: true, + type: 'error', + duration: 0 + }) + } } } } diff --git a/wms/nladmin-ui/src/views/system/version/index.vue b/wms/nladmin-ui/src/views/system/version/index.vue new file mode 100644 index 0000000..8104770 --- /dev/null +++ b/wms/nladmin-ui/src/views/system/version/index.vue @@ -0,0 +1,274 @@ + + + + +