This commit is contained in:
dzc committed 2025-08-12 16:32:28 +08:00
commit 9182fdcb77
23 files changed
+1165 -11

No files matched your search

@@ -0,0 +1,34 @@
package com.bsd.pjcloud.canteen.dto.canteenonline;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.bsd.pjcloud.common.core.util.TenantTable;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDateTime;
import java.util.List;
/**
* 在线点餐spu
*
* @author zjf
* @date 2024-12-16 23:42:19
*/
@Data
@Schema(description = "在线点餐批量关联新增每日菜谱")
public class CanteenOnlineSpuSaveDto {
/**
* 商户编号
*/
@Schema(description="商户编号")
private Long merchantId;
private List<CanteenOnlineSpuSaveItemDto> items;
}
@@ -0,0 +1,57 @@
package com.bsd.pjcloud.canteen.dto.canteenonline;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Data
public class CanteenOnlineSpuSaveItemDto {
/**
* 商品名称
*/
@Schema(description="商品名称")
private String name;
@Schema(description="关联每日菜谱id")
private Long linkSpuId;
/**
* 商品分类编号
*/
@Schema(description="商品分类编号")
private Long categoryId;
/**
* 商品封面图
*/
@Schema(description="商品封面图")
private String picUrl;
/**
* 排序字段
*/
@Schema(description="排序字段")
private Integer sort;
/**
* 商品状态: 1 上架(开启) 0 下架(禁用) -1 回收
*/
@Schema(description="商品状态: 1 上架(开启) 0 下架(禁用) -1 回收")
private Integer status;
/**
* 商品价格,单位使用:分
*/
@Schema(description="商品价格,单位使用:分")
private Integer price;
/**
* 库存
*/
@Schema(description="库存")
private Integer stock;
}
@@ -112,4 +112,7 @@ public class CanteenOnlineCategoryEntity extends Model<CanteenOnlineCategoryEnti
*/
@Schema(description = "商家id")
private Long merchantId;
@Schema(description = "餐段类型")
private String categoryType;
}
@@ -182,4 +182,7 @@ public class CanteenOnlineSpuEntity extends Model<CanteenOnlineSpuEntity> {
*/
@Schema(description="租户编号")
private Long tenantId;
@Schema(description="关联每日菜谱id")
private Long linkSpuId;
}
@@ -7,6 +7,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.canteen.dto.CanteenOnlineSpuUpdateStockReqDTO;
import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveDto;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.canteen.vo.app.CanteenOnlineSpuAppVO;
import com.bsd.pjcloud.common.core.util.BeanUtils;
@@ -58,7 +59,7 @@ public class CanteenOnlineSpuController {
LambdaQueryWrapper<CanteenOnlineSpuEntity> wrapper = Wrappers.lambdaQuery();
wrapper.like(StrUtil.isNotBlank(canteenOnlineSpu.getName()), CanteenOnlineSpuEntity::getName, canteenOnlineSpu.getName());
wrapper.eq(Objects.nonNull(canteenOnlineSpu.getCategoryId()), CanteenOnlineSpuEntity::getCategoryId, canteenOnlineSpu.getCategoryId());
return R.ok(canteenOnlineSpuService.page(page, wrapper));
return R.ok(canteenOnlineSpuService.pageScope(page, wrapper));
}
@@ -90,6 +91,14 @@ public class CanteenOnlineSpuController {
return R.ok(canteenOnlineSpuService.save(canteenOnlineSpu));
}
@Operation(summary = "批量新增关联每日菜谱", description = "批量新增关联每日菜谱")
@SysLog("批量导入新增点餐spu")
@PostMapping("/saveCanteenSpuBatch")
@PreAuthorize("@pms.hasPermission('canteen_canteenOnlineSpu_add')")
public R saveCanteenSpuBatch(@RequestBody CanteenOnlineSpuSaveDto canteenOnlineSpuSaveDto) {
return R.ok(canteenOnlineSpuService.saveCanteenSpuBatch(canteenOnlineSpuSaveDto));
}
/**
* 修改在线点餐spu
*
@@ -146,8 +155,9 @@ public class CanteenOnlineSpuController {
@GetMapping("/app/getCanteenSpuList")
@Operation(summary = "获取在线点餐的菜品列表", description = "1.通过分类id,可以不填,默认查全部,2.商户id,必传")
public R<List<APPOnlineSpuVO>> getCanteenSpuList(@RequestParam(value = "categoryId", required = false) String categoryId,
@RequestParam(value = "merchantId") String merchantId) {
return canteenOnlineSpuService.getCanteenOnlineSpuList(categoryId, merchantId);
@RequestParam(value = "merchantId") String merchantId,
@RequestParam(value = "time")String time) {
return canteenOnlineSpuService.getCanteenOnlineSpuList(categoryId, merchantId,time);
}
@@ -1,7 +1,10 @@
package com.bsd.pjcloud.canteen.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.canteen.dto.CanteenOnlineSpuUpdateStockReqDTO;
import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveDto;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.common.core.util.R;
@@ -18,7 +21,7 @@ public interface CanteenOnlineSpuService extends IService<CanteenOnlineSpuEntity
* @param merchantId
* @return
*/
R<List<APPOnlineSpuVO>> getCanteenOnlineSpuList(String categoryId, String merchantId);
R<List<APPOnlineSpuVO>> getCanteenOnlineSpuList(String categoryId, String merchantId,String time);
/**
@@ -27,4 +30,20 @@ public interface CanteenOnlineSpuService extends IService<CanteenOnlineSpuEntity
* @return
*/
R updateOnlineSpuSkuStock(CanteenOnlineSpuUpdateStockReqDTO canteenOnlineSpuUpdateStockReqDTO);
/**
* 批量新增关联每日菜谱
* @param canteenOnlineSpuSaveDto
* @return
*/
Object saveCanteenSpuBatch(CanteenOnlineSpuSaveDto canteenOnlineSpuSaveDto);
/**
* 分页查询
* @param page
* @param wrapper
* @return
*/
Object pageScope(Page page, LambdaQueryWrapper<CanteenOnlineSpuEntity> wrapper);
}
@@ -1,18 +1,33 @@
package com.bsd.pjcloud.canteen.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.canteen.dto.CanteenOnlineSpuUpdateStockReqDTO;
import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveDto;
import com.bsd.pjcloud.canteen.dto.canteenonline.CanteenOnlineSpuSaveItemDto;
import com.bsd.pjcloud.canteen.entity.CanteenCategoryEntity;
import com.bsd.pjcloud.canteen.entity.CanteenDailyMenuEntity;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineCategoryEntity;
import com.bsd.pjcloud.canteen.entity.CanteenOnlineSpuEntity;
import com.bsd.pjcloud.canteen.mapper.CanteenCategoryMapper;
import com.bsd.pjcloud.canteen.mapper.CanteenDailyMenuMapper;
import com.bsd.pjcloud.canteen.mapper.CanteenOnlineCategoryMapper;
import com.bsd.pjcloud.canteen.mapper.CanteenOnlineSpuMapper;
import com.bsd.pjcloud.canteen.service.CanteenOnlineSpuService;
import com.bsd.pjcloud.canteen.vo.APPOnlineSpuVO;
import com.bsd.pjcloud.common.core.util.BeanUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* 在线点餐spu
@@ -21,14 +36,33 @@ import java.util.List;
* @date 2024-12-16 23:42:19
*/
@Service
@AllArgsConstructor
public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMapper, CanteenOnlineSpuEntity> implements CanteenOnlineSpuService {
private final CanteenCategoryMapper categoryMapper;
private final CanteenOnlineCategoryMapper canteenOnlineCategoryMapper;
private final CanteenDailyMenuMapper canteenDailyMenuMapper;
@Override
public R<List<APPOnlineSpuVO>> getCanteenOnlineSpuList(String categoryId, String merchantId) {
public R<List<APPOnlineSpuVO>> getCanteenOnlineSpuList(String categoryId, String merchantId,String time) {
LambdaQueryWrapper<CanteenOnlineSpuEntity> canteenOnlineSpuEntityLambdaQueryWrapper = new LambdaQueryWrapper<>();
canteenOnlineSpuEntityLambdaQueryWrapper.eq(StringUtils.isNotEmpty(categoryId), CanteenOnlineSpuEntity::getCategoryId, categoryId);
canteenOnlineSpuEntityLambdaQueryWrapper.eq(CanteenOnlineSpuEntity::getMerchantId, merchantId);
List<CanteenOnlineSpuEntity> canteenSpuEntities = baseMapper.selectList(canteenOnlineSpuEntityLambdaQueryWrapper);
List<APPOnlineSpuVO> bean = BeanUtils.toBean(canteenSpuEntities, APPOnlineSpuVO.class);
//根据商户查询当前日期的每日菜谱,查询每日菜谱上架的菜的id,然后在从在线订餐的列表筛选出今天有上新的数据
LambdaQueryWrapper<CanteenDailyMenuEntity> dailyMenuEntityLambdaQueryWrapper = new LambdaQueryWrapper<>();
dailyMenuEntityLambdaQueryWrapper.eq(CanteenDailyMenuEntity::getMerchantId, merchantId);
dailyMenuEntityLambdaQueryWrapper.eq(CanteenDailyMenuEntity::getStartTime,time);
List<CanteenDailyMenuEntity> dailyMenuEntities = canteenDailyMenuMapper.selectList(dailyMenuEntityLambdaQueryWrapper);
List<Long> linkSpuIds = dailyMenuEntities.stream().map(d -> d.getSpuId()).collect(Collectors.toList());
//从canteenSpuEntities中筛选出今天有上新的数据
List<CanteenOnlineSpuEntity> finallyCanteenSpuEntities =
canteenSpuEntities.stream().filter(c -> {
return c.getLinkSpuId() == null || linkSpuIds.contains(c.getLinkSpuId());
}).collect(Collectors.toList());
List<APPOnlineSpuVO> bean = BeanUtils.toBean(finallyCanteenSpuEntities, APPOnlineSpuVO.class);
return R.ok(bean);
}
@@ -50,4 +84,39 @@ public class CanteenOnlineSpuServiceImpl extends ServiceImpl<CanteenOnlineSpuMap
}
return R.ok();
}
@Override
public Object saveCanteenSpuBatch(CanteenOnlineSpuSaveDto canteenOnlineSpuSaveDto) {
Long merchantId = canteenOnlineSpuSaveDto.getMerchantId();
List<CanteenOnlineSpuSaveItemDto> items = canteenOnlineSpuSaveDto.getItems();
List<CanteenOnlineSpuEntity> canteenSpuEntities = BeanUtils.toBean(items, CanteenOnlineSpuEntity.class);
//每日菜谱的分类,查询在线分类里面是否有
Set<Long> categoryIds = canteenSpuEntities.stream().map(c -> c.getCategoryId()).collect(Collectors.toSet());
List<CanteenCategoryEntity> canteenCategoryEntities = categoryMapper.selectList(new LambdaQueryWrapper<CanteenCategoryEntity>().select(CanteenCategoryEntity::getId).in(CanteenCategoryEntity::getId, categoryIds));
//把canteenCategoryEntities转为key map
Map<Long, CanteenCategoryEntity> canteenCategoryEntityMap = canteenCategoryEntities.stream().collect(Collectors.toMap(CanteenCategoryEntity::getId, c -> c));
Long deptId = SecurityUtils.getUser().getDeptId();
canteenSpuEntities.forEach(c -> {
CanteenCategoryEntity canteenCategoryEntity = canteenCategoryEntityMap.get(c.getCategoryId());
String name = canteenCategoryEntity.getName();
//查询分类里面是否有
CanteenOnlineCategoryEntity canteenOnlineCategoryEntity = canteenOnlineCategoryMapper.selectOne(new LambdaQueryWrapper<CanteenOnlineCategoryEntity>().eq(CanteenOnlineCategoryEntity::getName, name));
if (canteenOnlineCategoryEntity == null){
CanteenOnlineCategoryEntity bean = BeanUtils.toBean(canteenCategoryEntity, CanteenOnlineCategoryEntity.class);
canteenOnlineCategoryMapper.insert(bean);
c.setCategoryId(bean.getId());
}else {
c.setCategoryId(canteenOnlineCategoryEntity.getId());
}
c.setMerchantId(merchantId);
c.setDeptId(deptId);
});
return baseMapper.insert(canteenSpuEntities);
}
@Override
public Object pageScope(Page page, LambdaQueryWrapper<CanteenOnlineSpuEntity> wrapper) {
return baseMapper.selectPageByScope(page, wrapper, DataScope.of());
}
}
@@ -17,5 +17,7 @@
<result property="updateTime" column="update_time"/>
<result property="delFlag" column="del_flag"/>
<result property="tenantId" column="tenant_id"/>
<result property="merchantId" column="merchant_id"/>
<result property="categoryType" column="category_type"/>
</resultMap>
</mapper>
@@ -28,5 +28,6 @@
<result property="updateTime" column="update_time"/>
<result property="delFlag" column="del_flag"/>
<result property="tenantId" column="tenant_id"/>
<result property="linkSpuId" column="link_spu_id"/>
</resultMap>
</mapper>
@@ -6,6 +6,8 @@ import java.time.LocalDateTime;
@Data
public class NoticeScreenVo {
private Long id;
@Schema(description = "标题")
private String title;
@@ -0,0 +1,387 @@
# 外部系统滚动通知接口集成指南
## 📋 概述
本文档介绍如何集成使用 PJCloud 系统的滚动通知接口,该接口提供安全的 API 访问,供外部系统获取滚动通知信息。
## 🔐 认证方式
接口采用 **AppId + AppSecret** 的认证方式,通过 HTTP 请求头进行身份验证。
### 认证参数
| 参数名 | 位置 | 必填 | 说明 |
|--------|------|------|------|
| X-App-Id | 请求头 | 是 | 应用ID,由系统管理员分配 |
| X-App-Secret | 请求头 | 是 | 应用密钥,由系统管理员分配 |
## 🌐 接口信息
### 滚动通知接口
- **接口地址**: `GET /notice/external/scroll`
- **请求方式**: GET
- **认证方式**: 请求头认证
- **数据格式**: JSON
### 请求参数
#### 查询参数
| 参数名 | 必填 | 默认值 | 说明 |
|--------|------|--------|------|
| limit | 否 | 20 | 返回条数,最大50条 |
#### 请求头示例
```http
X-App-Id: 202501270001
X-App-Secret: 550e8400-e29b-41d4-a716-446655440001
```
## 📡 响应格式
### 成功响应
```json
{
"code": 0,
"msg": "操作成功",
"data": [
{
"title": "系统维护通知",
"createTime": "2025-01-27 10:00:00",
"description": "系统将于今晚进行维护升级"
},
{
"title": "新功能上线",
"createTime": "2025-01-26 15:30:00",
"description": "新增用户管理功能模块"
}
]
}
```
### 错误响应
```json
{
"code": 500,
"msg": "无效的应用ID或密钥",
"data": null
}
```
## 💻 集成示例
### JavaScript 调用示例
#### 基础调用
```javascript
// 获取滚动通知
async function getScrollNotices(limit = 20) {
try {
const response = await fetch('/notice/external/scroll?limit=' + limit, {
method: 'GET',
headers: {
'X-App-Id': '202501270001',
'X-App-Secret': '550e8400-e29b-41d4-a716-446655440001',
'Content-Type': 'application/json'
}
});
const result = await response.json();
if (result.code === 0) {
return result.data;
} else {
throw new Error(result.msg);
}
} catch (error) {
console.error('获取通知失败:', error);
throw error;
}
}
// 使用示例
getScrollNotices(15)
.then(notices => {
console.log('获取到通知:', notices);
// 处理通知数据
displayNotices(notices);
})
.catch(error => {
console.error('错误:', error.message);
});
```
#### 滚动展示组件
```javascript
// 滚动通知展示组件
class ScrollNoticeDisplay {
constructor(containerId, appId, appSecret) {
this.container = document.getElementById(containerId);
this.appId = appId;
this.appSecret = appSecret;
this.notices = [];
this.currentIndex = 0;
this.interval = null;
}
// 获取通知数据
async fetchNotices() {
try {
const response = await fetch('/notice/external/scroll?limit=10', {
headers: {
'X-App-Id': this.appId,
'X-App-Secret': this.appSecret
}
});
const result = await response.json();
if (result.code === 0) {
this.notices = result.data;
this.startScroll();
}
} catch (error) {
console.error('获取通知失败:', error);
}
}
// 开始滚动
startScroll() {
if (this.notices.length === 0) return;
this.interval = setInterval(() => {
this.showNextNotice();
}, 3000); // 每3秒切换一次
this.showNextNotice(); // 立即显示第一条
}
// 显示下一条通知
showNextNotice() {
if (this.notices.length === 0) return;
const notice = this.notices[this.currentIndex];
this.container.innerHTML = `
<div class="notice-item">
<span class="notice-title">${notice.title}</span>
<span class="notice-time">${notice.createTime}</span>
</div>
`;
this.currentIndex = (this.currentIndex + 1) % this.notices.length;
}
// 停止滚动
stopScroll() {
if (this.interval) {
clearInterval(this.interval);
this.interval = null;
}
}
}
// 使用示例
const noticeDisplay = new ScrollNoticeDisplay(
'notice-container',
'202501270001',
'550e8400-e29b-41d4-a716-446655440001'
);
noticeDisplay.fetchNotices();
```
### cURL 调用示例
#### 基础调用
```bash
curl -X GET "http://your-domain/notice/external/scroll?limit=15" \
-H "X-App-Id: 202501270001" \
-H "X-App-Secret: 550e8400-e29b-41d4-a716-446655440001"
```
#### 获取最大条数
```bash
curl -X GET "http://your-domain/notice/external/scroll?limit=50" \
-H "X-App-Id: 202501270001" \
-H "X-App-Secret: 550e8400-e29b-41d4-a716-446655440001"
```
### Java 调用示例
```java
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class NoticeApiClient {
private final String baseUrl;
private final String appId;
private final String appSecret;
private final RestTemplate restTemplate;
public NoticeApiClient(String baseUrl, String appId, String appSecret) {
this.baseUrl = baseUrl;
this.appId = appId;
this.appSecret = appSecret;
this.restTemplate = new RestTemplate();
}
public List<NoticeScreenVo> getScrollNotices(int limit) {
HttpHeaders headers = new HttpHeaders();
headers.set("X-App-Id", appId);
headers.set("X-App-Secret", appSecret);
HttpEntity<String> entity = new HttpEntity<>(headers);
String url = baseUrl + "/notice/external/scroll?limit=" + limit;
ResponseEntity<ApiResponse> response = restTemplate.exchange(
url,
HttpMethod.GET,
entity,
ApiResponse.class
);
if (response.getBody() != null && response.getBody().getCode() == 0) {
return response.getBody().getData();
}
throw new RuntimeException("获取通知失败");
}
}
// 使用示例
NoticeApiClient client = new NoticeApiClient(
"http://your-domain",
"202501270001",
"550e8400-e29b-41d4-a716-446655440001"
);
List<NoticeScreenVo> notices = client.getScrollNotices(20);
```
## 🎨 CSS 样式示例
```css
/* 滚动通知容器 */
.notice-container {
width: 100%;
height: 40px;
background: linear-gradient(90deg, #f8f9fa, #e9ecef);
border: 1px solid #dee2e6;
border-radius: 4px;
overflow: hidden;
position: relative;
}
/* 通知项 */
.notice-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 16px;
height: 100%;
animation: slideIn 0.5s ease-in-out;
}
/* 通知标题 */
.notice-title {
font-weight: 500;
color: #495057;
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
/* 通知时间 */
.notice-time {
font-size: 12px;
color: #6c757d;
margin-left: 16px;
}
/* 滑动动画 */
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 响应式设计 */
@media (max-width: 768px) {
.notice-container {
height: 50px;
}
.notice-item {
padding: 12px 16px;
}
.notice-time {
display: none; /* 移动端隐藏时间 */
}
}
```
## ⚠️ 注意事项
### 安全要求
1. **密钥保护**: 请妥善保管 AppSecret,不要泄露给第三方
2. **HTTPS 传输**: 建议使用 HTTPS 协议进行数据传输
3. **访问频率**: 建议控制调用频率,避免频繁请求
### 技术限制
1. **返回条数**: 单次请求最多返回 50 条通知
2. **数据时效**: 只返回已发布的公告信息
3. **排序规则**: 按置顶状态和创建时间倒序排列
### 错误处理
1. **认证失败**: 检查 AppId 和 AppSecret 是否正确
2. **网络异常**: 实现重试机制和超时处理
3. **数据异常**: 对返回数据进行有效性验证
## 🔧 故障排除
### 常见问题
#### 1. 认证失败
**错误信息**: "无效的应用ID或密钥"
**解决方案**:
- 检查 AppId 和 AppSecret 是否正确
- 确认应用是否已启用
- 联系系统管理员验证配置
#### 2. 网络超时
**错误信息**: 请求超时或无响应
**解决方案**:
- 检查网络连接
- 确认服务器地址是否正确
- 实现请求超时和重试机制
#### 3. 数据为空
**可能原因**:
- 当前没有已发布的公告
- 查询参数设置不当
- 系统维护中
## 📞 技术支持
如果在集成过程中遇到问题,请联系技术支持:
- **技术文档**: 本文档
- **API 地址**: `/notice/external/scroll`
- **认证方式**: AppId + AppSecret
- **数据格式**: JSON
---
*最后更新时间: 2025-01-27*
*版本: v1.0*
@@ -0,0 +1,98 @@
# 外部系统滚动通知接口使用说明
## 接口概述
提供安全的API访问,供其他系统获取滚动通知信息。
## 接口信息
- **接口地址**: `GET /notice/external/scroll`
- **请求方式**: GET
- **认证方式**: 请求头认证
## 请求参数
### 请求头
| 参数名 | 必填 | 说明 |
|--------|------|------|
| X-App-Id | 是 | 应用ID,在配置文件中配置 |
| X-App-Secret | 是 | 应用密钥,在配置文件中配置 |
### 查询参数
| 参数名 | 必填 | 默认值 | 说明 |
|--------|------|--------|------|
| limit | 否 | 20 | 返回条数,最大50条 |
## 响应格式
```json
{
"code": 0,
"msg": "操作成功",
"data": [
{
"title": "通知标题",
"createTime": "2025-01-27 10:00:00",
"description": "通知描述"
}
]
}
```
## 使用示例
### JavaScript 调用示例
```javascript
fetch('/notice/external/scroll?limit=15', {
method: 'GET',
headers: {
'X-App-Id': 'external_app_001',
'X-App-Secret': 'secret_2024_001'
}
})
.then(response => response.json())
.then(data => {
if (data.code === 0) {
// 处理通知数据
console.log(data.data);
}
})
.catch(error => {
console.error('获取通知失败:', error);
});
```
### cURL 调用示例
```bash
curl -X GET "http://your-domain/notice/external/scroll?limit=10" \
-H "X-App-Id: external_app_001" \
-H "X-App-Secret: secret_2024_001"
```
## 配置说明
在 `application.yml` 中添加以下配置:
```yaml
pjcloud:
external-app:
apps:
- appId: "your_app_id"
appSecret: "your_app_secret"
appName: "你的应用名称"
enabled: true
```
## 安全特性
1. **应用认证**: 通过 appId 和 appSecret 验证调用方身份
2. **访问控制**: 只返回已发布的公告
3. **数据限制**: 限制最大返回条数,防止恶意调用
4. **信息脱敏**: 只返回必要的滚动信息
## 注意事项
1. 请妥善保管应用密钥,不要泄露给第三方
2. 建议使用HTTPS协议进行传输
3. 可以根据需要调整返回条数限制
4. 支持动态启用/禁用应用访问权限
@@ -0,0 +1,51 @@
package com.bsd.pjcloud.integrated.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* 外部应用配置
* 用于管理外部系统的应用ID和密钥
*
* @author zjf
* @date 2025-01-27
*/
@Data
@Component
@ConfigurationProperties(prefix = "pjcloud.external-app")
public class ExternalAppConfig {
/**
* 外部应用列表
*/
private List<AppCredential> apps;
/**
* 应用凭证
*/
@Data
public static class AppCredential {
/**
* 应用ID
*/
private String appId;
/**
* 应用密钥
*/
private String appSecret;
/**
* 应用名称
*/
private String appName;
/**
* 是否启用
*/
private Boolean enabled = true;
}
}
@@ -0,0 +1,139 @@
package com.bsd.pjcloud.integrated.controller;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.integrated.entity.CleanUserEntity;
import com.bsd.pjcloud.integrated.service.CleanUserService;
import org.springframework.security.access.prepost.PreAuthorize;
import com.bsd.pjcloud.common.excel.annotation.ResponseExcel;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Objects;
/**
* 保洁信息
*
* @author zjf
* @date 2025-08-11 11:07:52
*/
@RestController
@RequiredArgsConstructor
@RequestMapping("/cleanUser" )
@Tag(description = "cleanUser" , name = "保洁信息管理" )
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class CleanUserController {
private final CleanUserService cleanUserService;
/**
* 分页查询
* @param page 分页对象
* @param cleanUser 保洁信息
* @return
*/
@Operation(summary = "分页查询" , description = "分页查询" )
@GetMapping("/page" )
@PreAuthorize("@pms.hasPermission('integrated_cleanUser_view')" )
public R getCleanUserPage(@ParameterObject Page page, @ParameterObject CleanUserEntity cleanUser) {
LambdaQueryWrapper<CleanUserEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(StrUtil.isNotBlank(cleanUser.getName()),CleanUserEntity::getName,cleanUser.getName());
wrapper.eq(StrUtil.isNotBlank(cleanUser.getPhone()),CleanUserEntity::getPhone,cleanUser.getPhone());
return R.ok(cleanUserService.page(page, wrapper));
}
/**
* 通过id查询保洁信息
* @param id id
* @return R
*/
@Operation(summary = "通过id查询" , description = "通过id查询" )
@GetMapping("/{id}" )
@PreAuthorize("@pms.hasPermission('integrated_cleanUser_view')" )
public R getById(@PathVariable("id" ) Long id) {
return R.ok(cleanUserService.getById(id));
}
/**
* 新增保洁信息
* @param cleanUser 保洁信息
* @return R
*/
@Operation(summary = "新增保洁信息" , description = "新增保洁信息" )
@SysLog("新增保洁信息" )
@PostMapping
@PreAuthorize("@pms.hasPermission('integrated_cleanUser_add')" )
public R save(@RequestBody CleanUserEntity cleanUser) {
cleanUser.setDeptId(SecurityUtils.getUser().getDeptId());
return R.ok(cleanUserService.save(cleanUser));
}
/**
* 修改保洁信息
* @param cleanUser 保洁信息
* @return R
*/
@Operation(summary = "修改保洁信息" , description = "修改保洁信息" )
@SysLog("修改保洁信息" )
@PutMapping
@PreAuthorize("@pms.hasPermission('integrated_cleanUser_edit')" )
public R updateById(@RequestBody CleanUserEntity cleanUser) {
return R.ok(cleanUserService.updateById(cleanUser));
}
/**
* 通过id删除保洁信息
* @param ids id列表
* @return R
*/
@Operation(summary = "通过id删除保洁信息" , description = "通过id删除保洁信息" )
@SysLog("通过id删除保洁信息" )
@DeleteMapping
@PreAuthorize("@pms.hasPermission('integrated_cleanUser_del')" )
public R removeById(@RequestBody Long[] ids) {
return R.ok(cleanUserService.removeBatchByIds(CollUtil.toList(ids)));
}
/**
* 导出excel 表格
* @param cleanUser 查询条件
* @param ids 导出指定ID
* @return excel 文件流
*/
@ResponseExcel
@GetMapping("/export")
@PreAuthorize("@pms.hasPermission('integrated_cleanUser_export')" )
public List<CleanUserEntity> export(CleanUserEntity cleanUser,Long[] ids) {
return cleanUserService.list(Wrappers.lambdaQuery(cleanUser).in(ArrayUtil.isNotEmpty(ids), CleanUserEntity::getId, ids));
}
/**
* 获取保洁用户下拉列表
* @return R
*/
@Operation(summary = "获取保洁用户下拉列表", description = "获取保洁用户下拉列表")
@GetMapping("/dropdown")
// @PreAuthorize("@pms.hasPermission('integrated_cleanUser_view')")
public R getCleanUserDropdown() {
LambdaQueryWrapper<CleanUserEntity> wrapper = Wrappers.lambdaQuery();
wrapper.select(CleanUserEntity::getId, CleanUserEntity::getName, CleanUserEntity::getPhone);
wrapper.orderByAsc(CleanUserEntity::getName);
List<CleanUserEntity> list = cleanUserService.list(wrapper);
return R.ok(list);
}
}
@@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.log.annotation.SysLog;
import com.bsd.pjcloud.common.security.annotation.Inner;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
import com.bsd.pjcloud.integrated.entity.NoticeEntity;
import com.bsd.pjcloud.integrated.service.NoticeService;
@@ -22,6 +23,7 @@ import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import com.bsd.pjcloud.integrated.vo.notice.NoticeScreenVo;
import java.util.stream.Collectors;
import com.bsd.pjcloud.integrated.config.ExternalAppConfig;
import java.util.List;
import java.util.Objects;
@@ -40,6 +42,7 @@ import java.util.Objects;
public class NoticeController {
private final NoticeService noticeService;
private final ExternalAppConfig externalAppConfig;
/**
* 分页查询
@@ -192,4 +195,72 @@ public class NoticeController {
}).collect(Collectors.toList());
return R.ok(voList);
}
/**
* 外部系统滚动通知接口
* 提供安全的API访问,用于其他系统展示滚动通知
* @param appId 应用ID
* @param appSecret 应用密钥
* @param limit 返回条数限制,默认20条
* @return R
*/
@Operation(summary = "外部系统滚动通知接口", description = "提供安全的API访问,用于其他系统展示滚动通知")
@GetMapping("/external/scroll")
@Inner(value = false)
public R getExternalScrollNotices(
@RequestHeader("X-App-Id") String appId,
@RequestHeader("X-App-Secret") String appSecret,
@RequestParam(defaultValue = "20") Integer limit) {
// 验证应用ID和密钥
if (!isValidAppCredentials(appId, appSecret)) {
return R.failed("无效的应用ID或密钥");
}
// 限制最大返回条数,防止恶意调用
if (limit > 50) {
limit = 50;
}
LambdaQueryWrapper<NoticeEntity> wrapper = Wrappers.lambdaQuery();
wrapper.eq(NoticeEntity::getStatus, "0"); // 只查询已发布的公告
wrapper.orderByDesc(NoticeEntity::getIsTop, NoticeEntity::getCreateTime); // 置顶优先,时间倒序
wrapper.last("limit " + limit);
List<NoticeEntity> list = noticeService.list(wrapper);
// 转换为简化的VO对象,只返回必要信息
List<NoticeScreenVo> voList = list.stream().map(e -> {
NoticeScreenVo vo = new NoticeScreenVo();
vo.setId(e.getId());
vo.setTitle(e.getTitle());
vo.setCreateTime(e.getCreateTime());
vo.setDescription(e.getDescription());
return vo;
}).collect(Collectors.toList());
return R.ok(voList);
}
/**
* 验证应用ID和密钥
* @param appId 应用ID
* @param appSecret 应用密钥
* @return 是否有效
*/
private boolean isValidAppCredentials(String appId, String appSecret) {
// 从配置中读取有效的应用ID和密钥
if (StrUtil.isBlank(appId) || StrUtil.isBlank(appSecret)) {
return false;
}
// 从配置中验证应用凭证
if (externalAppConfig.getApps() != null) {
return externalAppConfig.getApps().stream()
.filter(ExternalAppConfig.AppCredential::getEnabled)
.anyMatch(app -> appId.equals(app.getAppId()) && appSecret.equals(app.getAppSecret()));
}
return false;
}
}
@@ -0,0 +1,97 @@
package com.bsd.pjcloud.integrated.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import com.bsd.pjcloud.common.core.util.TenantTable;
import java.time.LocalDateTime;
/**
* 保洁信息
*
* @author zjf
* @date 2025-08-11 11:07:52
*/
@Data
@TenantTable
@TableName("clean_user")
@EqualsAndHashCode(callSuper = true)
@Schema(description = "保洁信息")
public class CleanUserEntity extends Model<CleanUserEntity> {
/**
* id
*/
@TableId(type = IdType.ASSIGN_ID)
@Schema(description="id")
private Long id;
/**
* 姓名
*/
@Schema(description="姓名")
private String name;
/**
* 手机号
*/
@Schema(description="手机号")
private String phone;
/**
* 部门id
*/
@Schema(description="部门id")
private Long deptId;
/**
* 照片
*/
@Schema(description="照片")
private String picUrl;
/**
* 创建者
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description="创建者")
private String createBy;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
@Schema(description="创建时间")
private LocalDateTime createTime;
/**
* 更新者
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description="更新者")
private String updateBy;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description="更新时间")
private LocalDateTime updateTime;
/**
* 是否删除
*/
@TableLogic
@TableField(fill = FieldFill.INSERT)
@Schema(description="是否删除")
private String delFlag;
/**
* 租户编号
*/
@Schema(description="租户编号")
private Long tenantId;
}
@@ -0,0 +1,11 @@
package com.bsd.pjcloud.integrated.mapper;
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
import com.bsd.pjcloud.integrated.entity.CleanUserEntity;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface CleanUserMapper extends PjcloudBaseMapper<CleanUserEntity> {
}
@@ -0,0 +1,8 @@
package com.bsd.pjcloud.integrated.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.integrated.entity.CleanUserEntity;
public interface CleanUserService extends IService<CleanUserEntity> {
}
@@ -0,0 +1,16 @@
package com.bsd.pjcloud.integrated.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.integrated.entity.CleanUserEntity;
import com.bsd.pjcloud.integrated.mapper.CleanUserMapper;
import com.bsd.pjcloud.integrated.service.CleanUserService;
import org.springframework.stereotype.Service;
/**
* 保洁信息
*
* @author zjf
* @date 2025-08-11 11:07:52
*/
@Service
public class CleanUserServiceImpl extends ServiceImpl<CleanUserMapper, CleanUserEntity> implements CleanUserService {
}
@@ -0,0 +1,29 @@
# 外部应用配置示例
# 将此配置添加到你的 application.yml 或 application-{profile}.yml 文件中
pjcloud:
external-app:
apps:
# 外部系统1
- appId: "202501270001"
appSecret: "550e8400-e29b-41d4-a716-446655440001"
appName: "外部系统A"
enabled: true
# 外部系统2
- appId: "202501270002"
appSecret: "550e8400-e29b-41d4-a716-446655440002"
appName: "外部系统B"
enabled: true
# 外部系统3
- appId: "202501270003"
appSecret: "550e8400-e29b-41d4-a716-446655440003"
appName: "外部系统C"
enabled: true
# 外部系统4(已禁用)
- appId: "202501270004"
appSecret: "550e8400-e29b-41d4-a716-446655440004"
appName: "外部系统D"
enabled: false
@@ -26,3 +26,11 @@ spring:
visitor:
jwt-secret: "u8Jk2n3Lk4pQ5r6T7s8V9w0X1y2Z3a4B"
qr-secret: "Qw8eR2t9Yp3sV6bN1mZ4xT7cL0vB5nH2" # 新增二维码secret,随机生成
pjcloud:
external-app:
apps:
- appId: "202501270001"
appSecret: "550e8400-e29b-41d4-a716-446655440001"
appName: "通知公告"
enabled: true
@@ -0,0 +1,19 @@
<?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="com.bsd.pjcloud.integrated.mapper.CleanUserMapper">
<resultMap id="cleanUserMap" type="com.bsd.pjcloud.integrated.entity.CleanUserEntity">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="phone" column="phone"/>
<result property="deptId" column="dept_id"/>
<result property="picUrl" column="pic_url"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
<result property="updateBy" column="update_by"/>
<result property="updateTime" column="update_time"/>
<result property="delFlag" column="del_flag"/>
<result property="tenantId" column="tenant_id"/>
</resultMap>
</mapper>
@@ -107,6 +107,10 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl<SmErpInventory
List<SmErpInventoryRecordsItemEntity> cgPlanItemEntities = BeanUtils.toBean(items, SmErpInventoryRecordsItemEntity.class);
List<UpdateStockDTO> updateStockDTOS = new ArrayList<>();
Set<Long> units = cgPlanItemEntities.stream().map(s -> s.getUnitId()).collect(Collectors.toSet());
List<SmErpUnitEntity> smErpUnitEntities = smErpUnitMapper.selectList(new LambdaQueryWrapper<SmErpUnitEntity>().in(SmErpUnitEntity::getId, units));
Map<Long, SmErpUnitEntity> unitMap = smErpUnitEntities.stream().collect(Collectors.toMap(SmErpUnitEntity::getId, unit -> unit));
for (SmErpInventoryRecordsItemEntity cgPlanItemEntity : cgPlanItemEntities) {
cgPlanItemEntity.setInventoryId(smErpCgDdEntity.getId());
cgPlanItemEntity.setDeptId(deptId);
@@ -115,10 +119,11 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl<SmErpInventory
Double quantity = cgPlanItemEntity.getQuantity();
String orderType = smErpCgDdEntity.getOrderType();
cgPlanItemEntity.setOrderType(orderType);
Integer packageNum = unitMap.get(cgPlanItemEntity.getUnitId()).getPackageNum();
if (orderType.equals(OrderTypeEnum.RK.getType())){
updateStockDTO.setIncrCount(quantity);
updateStockDTO.setIncrCount(packageNum*quantity);
}else if (orderType.equals(OrderTypeEnum.CK.getType())){
updateStockDTO.setIncrCount(-quantity);
updateStockDTO.setIncrCount(-packageNum*quantity);
}else if (orderType.equals(OrderTypeEnum.No.getType())){
//无需更新库存
}else {
@@ -460,6 +465,9 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl<SmErpInventory
List<InventoryRecordEditItemDTO> items = inventoryRecordEditDTO.getItems();
List<UpdateStockDTO> updateStockDTOS = new ArrayList<>();
List<SmErpInventoryRecordsItemEntity> smErpInventoryRecordsItemEntityList = new ArrayList<>();
Set<Long> units = items.stream().map(s -> s.getUnitId()).collect(Collectors.toSet());
List<SmErpUnitEntity> smErpUnitEntities = smErpUnitMapper.selectList(new LambdaQueryWrapper<SmErpUnitEntity>().in(SmErpUnitEntity::getId, units));
Map<Long, SmErpUnitEntity> unitMap = smErpUnitEntities.stream().collect(Collectors.toMap(SmErpUnitEntity::getId, unit -> unit));
for (InventoryRecordEditItemDTO item : items){
SmErpInventoryRecordsItemEntity smErpInventoryRecordsItemEntity = new SmErpInventoryRecordsItemEntity();
@@ -470,10 +478,11 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl<SmErpInventory
Double quantity = item.getQuantity();
String orderType = item.getOrderType();
smErpInventoryRecordsItemEntity.setOrderType(orderType);
Integer packageNum = unitMap.get(item.getUnitId()).getPackageNum();
if (orderType.equals(OrderTypeEnum.RK.getType())){
updateStockDTO.setIncrCount(quantity);
updateStockDTO.setIncrCount(packageNum*quantity);
}else if (orderType.equals(OrderTypeEnum.CK.getType())){
updateStockDTO.setIncrCount(-quantity);
updateStockDTO.setIncrCount(packageNum*-quantity);
}else {
throw new RuntimeException("订单类型错误");
}
@@ -499,11 +508,22 @@ public class SmErpInventoryRecordsServiceImpl extends ServiceImpl<SmErpInventory
List<SmErpInventoryRecordsItemEntity> dataBaseSmErpInventoryRecordsItemEntityList = smErpInventoryRecordsItemMapper
.selectList(new LambdaQueryWrapper<SmErpInventoryRecordsItemEntity>().eq(SmErpInventoryRecordsItemEntity::getInventoryId, inventoryRecordEditDTO.getId()));
Set<Long> unitsRemoved = dataBaseSmErpInventoryRecordsItemEntityList.stream().map(s -> s.getUnitId()).collect(Collectors.toSet());
List<SmErpUnitEntity> smErpUnitEntitiesRemoved = smErpUnitMapper.selectList(new LambdaQueryWrapper<SmErpUnitEntity>().in(SmErpUnitEntity::getId, unitsRemoved));
Map<Long, SmErpUnitEntity> unitMapRemoved = smErpUnitEntitiesRemoved.stream().collect(Collectors.toMap(SmErpUnitEntity::getId, unit -> unit));
//这部分是数据库原本添加的库存数据,需要全部减掉,重新添加更新的库存数据进去
dataBaseSmErpInventoryRecordsItemEntityList.forEach(item -> {
Integer packageNum = unitMapRemoved.get(item.getUnitId()).getPackageNum();
UpdateStockDTO updateStockDTO = new UpdateStockDTO();
updateStockDTO.setId(item.getErpSpuId());
updateStockDTO.setIncrCount(-item.getQuantity());
if (item.getOrderType().equals(OrderTypeEnum.RK.getType())){
updateStockDTO.setIncrCount(-packageNum *item.getQuantity());
}
if (item.getOrderType().equals(OrderTypeEnum.CK.getType())){
updateStockDTO.setIncrCount(packageNum *item.getQuantity());
}
updateStockDTOS.add(updateStockDTO);
});