jiaxing.zhou

Merge remote-tracking branch 'origin/dev'

......@@ -27,7 +27,55 @@
- `message`: 响应消息
- `data`: 响应数据,具体内容根据接口而定
## 1. 认证管理 (AuthController)
## 1. 首页仪表板 (DashboardController)
### 1.1 获取首页统计数据
**接口路径:** `GET /api/dashboard/stats`
**功能描述:** 获取首页仪表板的统计数据,包括今日订单、待出库、待开票、销售额等信息
**请求头:**
```
Authorization: Bearer <JWT_TOKEN>
```
**响应示例:**
```json
{
"code": 200,
"message": "获取统计数据成功",
"data": {
"todayOrderCount": 23,
"pendingDeliveryCount": 8,
"pendingInvoiceCount": 5,
"todaySalesAmount": 125680.50,
"onlineUserCount": 156,
"systemStatus": "正常运行",
"systemVersion": "v1.0.0"
}
}
```
**错误响应示例:**
```json
{
"code": 500,
"message": "获取统计数据失败: 数据库连接异常",
"data": null
}
```
**响应字段说明:**
- `todayOrderCount`: 今日订单数量
- `pendingDeliveryCount`: 待出库数量
- `pendingInvoiceCount`: 待开票数量
- `todaySalesAmount`: 今日销售额
- `onlineUserCount`: 在线用户数
- `systemStatus`: 系统状态
- `systemVersion`: 系统版本
## 2. 认证管理 (AuthController)
### 1.1 用户登录
......
package com.apple.erp.config;
import com.apple.erp.utils.JwtUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -34,9 +32,6 @@ import java.util.Arrays;
public class SecurityConfig {
@Autowired
private JwtUtils jwtUtils;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
......@@ -72,6 +67,7 @@ public class SecurityConfig {
.antMatchers("/api/auth/**").permitAll()
.antMatchers("/api/public/**").permitAll()
.antMatchers("/api/test/public").permitAll() // 允许测试接口
.antMatchers("/api/dashboard/**").authenticated() // 首页统计需要认证
.antMatchers("/actuator/**").permitAll()
.antMatchers("/druid/**").permitAll()
.antMatchers("/swagger-ui/**").permitAll()
......
......@@ -9,6 +9,7 @@ import com.apple.erp.dto.response.UserInfoRes;
import com.apple.erp.entity.SysUser;
import com.apple.erp.service.SysUserService;
import com.apple.erp.service.SysLogService;
import com.apple.erp.service.SessionService;
import com.apple.erp.utils.JwtUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
......@@ -24,7 +25,9 @@ import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 认证控制器
......@@ -51,6 +54,9 @@ public class AuthController {
@Autowired
private SysLogService sysLogService;
@Autowired
private SessionService sessionService;
/**
* 用户登录
......@@ -95,6 +101,21 @@ public class AuthController {
// 生成JWT令牌
String token = jwtUtils.generateToken(username);
String refreshToken = jwtUtils.generateRefreshToken(username);
// 将用户会话信息存储到Redis
try {
Map<String, Object> sessionData = new HashMap<>();
sessionData.put("username", username);
sessionData.put("loginTime", System.currentTimeMillis());
sessionData.put("clientIp", getClientIp(request));
sessionData.put("token", token);
sessionService.storeUserSession(username, sessionData);
log.info("用户会话已存储: {}", username);
} catch (Exception e) {
log.error("存储用户会话失败", e);
// Redis失败不影响登录流程
}
LoginRes loginResponse = new LoginRes(token, refreshToken, username);
ApiRes<LoginRes> response = ApiRes.success("登录成功", loginResponse);
......@@ -192,6 +213,16 @@ public class AuthController {
}
}
// 清除Redis中的用户会话
if (username != null) {
try {
sessionService.removeUserSession(username);
log.info("用户会话已清除: {}", username);
} catch (Exception e) {
log.error("清除用户会话失败", e);
}
}
// 清除Spring Security上下文
SecurityContextHolder.clearContext();
......
package com.apple.erp.controller;
import com.apple.erp.dto.response.ApiRes;
import com.apple.erp.dto.response.DashboardStatsRes;
import com.apple.erp.service.DashboardService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
/**
* 首页仪表板控制器
* 提供首页统计数据接口
*/
@Slf4j
@RestController
@RequestMapping("/api/dashboard")
public class DashboardController {
@Autowired
private DashboardService dashboardService;
/**
* 获取首页统计数据
* @return 统计数据
*/
@GetMapping("/stats")
public ApiRes<DashboardStatsRes> getDashboardStats() {
log.info("获取首页统计数据请求");
try {
DashboardStatsRes result = dashboardService.getDashboardStats();
log.info("首页统计数据获取成功: {}", result);
return ApiRes.success("获取统计数据成功", result);
} catch (Exception e) {
log.error("获取首页统计数据失败", e);
return ApiRes.error(500, "获取统计数据失败: " + e.getMessage());
}
}
/**
* 测试接口
* @return 测试数据
*/
@GetMapping("/test")
public ApiRes<DashboardStatsRes> getTestStats() {
log.info("测试接口调用");
DashboardStatsRes stats = new DashboardStatsRes();
stats.setTodayOrderCount(10);
stats.setPendingDeliveryCount(5);
stats.setPendingInvoiceCount(3);
stats.setTodaySalesAmount(10000.0);
stats.setOnlineUserCount(100);
stats.setSystemStatus("测试正常");
stats.setSystemVersion("v1.0.0");
return ApiRes.success("测试成功", stats);
}
/**
* 获取最近活动
* @return 最近活动列表
*/
@GetMapping("/activities")
public ApiRes<List<Map<String, Object>>> getRecentActivities() {
log.info("获取最近活动请求");
try {
List<Map<String, Object>> activities = dashboardService.getRecentActivities();
return ApiRes.success("获取最近活动成功", activities);
} catch (Exception e) {
log.error("获取最近活动失败", e);
return ApiRes.error(500, "获取最近活动失败: " + e.getMessage());
}
}
}
package com.apple.erp.dto.response;
import lombok.Data;
/**
* 首页统计数据响应DTO
*/
@Data
public class DashboardStatsRes {
/**
* 今日订单数量
*/
private Integer todayOrderCount;
/**
* 待出库数量
*/
private Integer pendingDeliveryCount;
/**
* 待开票数量
*/
private Integer pendingInvoiceCount;
/**
* 今日销售额
*/
private Double todaySalesAmount;
/**
* 在线用户数
*/
private Integer onlineUserCount;
/**
* 系统状态
*/
private String systemStatus;
/**
* 系统版本
*/
private String systemVersion;
/**
* 今日订单增长率
*/
private String orderGrowthRate;
/**
* 今日销售额增长率
*/
private String salesGrowthRate;
}
package com.apple.erp.service;
import com.apple.erp.dto.response.DashboardStatsRes;
import java.util.List;
import java.util.Map;
/**
* 首页仪表板服务接口
*/
public interface DashboardService {
/**
* 获取首页统计数据
* @return 统计数据
*/
DashboardStatsRes getDashboardStats();
/**
* 获取最近活动
* @return 最近活动列表
*/
List<Map<String, Object>> getRecentActivities();
}
......@@ -67,6 +67,12 @@ public interface DeliveryMainService extends IService<DeliveryMain> {
* @return 是否成功
*/
boolean updateDeliveryStatus(Long deliveryId, Integer deliveryStatus);
/**
* 获取待出库数量
* @return 待出库数量
*/
Integer getPendingDeliveryCount();
}
......
......@@ -67,6 +67,12 @@ public interface InvoiceMainService extends IService<InvoiceMain> {
* @return 是否成功
*/
boolean updateInvoiceStatus(Long invoiceId, Integer invoiceStatus);
/**
* 获取待开票数量
* @return 待开票数量
*/
Integer getPendingInvoiceCount();
}
......
......@@ -92,5 +92,21 @@ public interface OrderMainService extends IService<OrderMain> {
* @return 是否成功
*/
boolean updateRebateCalcFlag(Long orderId, Integer rebateCalcFlag);
/**
* 获取今日订单数量
*
* @param date 日期
* @return 订单数量
*/
Integer getTodayOrderCount(String date);
/**
* 获取今日销售额
*
* @param date 日期
* @return 销售额
*/
Double getTodaySalesAmount(String date);
}
......
package com.apple.erp.service;
import java.util.Map;
/**
* 会话管理服务接口
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
public interface SessionService {
/**
* 存储用户会话信息
* @param username 用户名
* @param sessionData 会话数据
*/
void storeUserSession(String username, Map<String, Object> sessionData);
/**
* 获取用户会话信息
* @param username 用户名
* @return 会话数据
*/
Map<String, Object> getUserSession(String username);
/**
* 删除用户会话
* @param username 用户名
*/
void removeUserSession(String username);
/**
* 获取在线用户数
* @return 在线用户数
*/
Integer getOnlineUserCount();
/**
* 清理过期会话
*/
void cleanupExpiredSessions();
}
......@@ -227,6 +227,14 @@ public class DeliveryMainServiceImpl extends ServiceImpl<DeliveryMainMapper, Del
return null;
}
}
@Override
public Integer getPendingDeliveryCount() {
LambdaQueryWrapper<DeliveryMain> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(DeliveryMain::getDelFlag, "0")
.eq(DeliveryMain::getDeliveryStatus, 0); // 0表示待出库
return Math.toIntExact(count(wrapper));
}
}
......
......@@ -229,4 +229,12 @@ public class InvoiceMainServiceImpl extends ServiceImpl<InvoiceMainMapper, Invoi
}
}
@Override
public Integer getPendingInvoiceCount() {
LambdaQueryWrapper<InvoiceMain> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(InvoiceMain::getDelFlag, "0")
.eq(InvoiceMain::getInvoiceStatus, 0); // 0表示待开票
return Math.toIntExact(count(wrapper));
}
}
......
......@@ -288,5 +288,39 @@ public class OrderMainServiceImpl extends ServiceImpl<OrderMainMapper, OrderMain
order.setUpdateTime(LocalDateTime.now());
return orderMainMapper.updateById(order) > 0;
}
@Override
public Integer getTodayOrderCount(String date) {
try {
LambdaQueryWrapper<OrderMain> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderMain::getDelFlag, "0")
.like(OrderMain::getCreateTime, date);
long count = count(wrapper);
System.out.println("今日订单数量查询结果: " + count + ", 日期: " + date);
return Math.toIntExact(count);
} catch (Exception e) {
System.err.println("查询今日订单数量失败: " + e.getMessage());
return 0;
}
}
@Override
public Double getTodaySalesAmount(String date) {
try {
LambdaQueryWrapper<OrderMain> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(OrderMain::getDelFlag, "0")
.like(OrderMain::getCreateTime, date);
List<OrderMain> orders = list(wrapper);
double totalAmount = orders.stream()
.mapToDouble(order -> order.getTotalAmount() != null ? order.getTotalAmount().doubleValue() : 0.0)
.sum();
System.out.println("今日销售额查询结果: " + totalAmount + ", 日期: " + date);
return totalAmount;
} catch (Exception e) {
System.err.println("查询今日销售额失败: " + e.getMessage());
return 0.0;
}
}
}
......
package com.apple.erp.service.impl;
import com.apple.erp.service.SessionService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* 会话管理服务实现
*
* @author Apple ERP Team
* @version 1.0.0
* @since 2024-01-01
*/
@Slf4j
@Service
public class SessionServiceImpl implements SessionService {
private static final String SESSION_KEY_PREFIX = "user:session:";
private static final long SESSION_TIMEOUT = 1800; // 30分钟
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
public void storeUserSession(String username, Map<String, Object> sessionData) {
try {
String sessionKey = SESSION_KEY_PREFIX + username;
redisTemplate.opsForValue().set(sessionKey, sessionData, SESSION_TIMEOUT);
log.debug("用户会话已存储: {}", username);
} catch (Exception e) {
log.error("存储用户会话失败: {}", username, e);
}
}
@Override
public Map<String, Object> getUserSession(String username) {
try {
String sessionKey = SESSION_KEY_PREFIX + username;
Object sessionData = redisTemplate.opsForValue().get(sessionKey);
if (sessionData instanceof Map) {
return (Map<String, Object>) sessionData;
}
} catch (Exception e) {
log.error("获取用户会话失败: {}", username, e);
}
return new HashMap<>();
}
@Override
public void removeUserSession(String username) {
try {
String sessionKey = SESSION_KEY_PREFIX + username;
redisTemplate.delete(sessionKey);
log.debug("用户会话已删除: {}", username);
} catch (Exception e) {
log.error("删除用户会话失败: {}", username, e);
}
}
@Override
public Integer getOnlineUserCount() {
try {
String pattern = SESSION_KEY_PREFIX + "*";
Set<String> sessionKeys = redisTemplate.keys(pattern);
if (sessionKeys != null) {
int onlineCount = sessionKeys.size();
log.debug("当前在线用户数: {}", onlineCount);
return onlineCount;
} else {
log.warn("Redis中未找到用户会话数据");
return 0;
}
} catch (Exception e) {
log.error("获取在线用户数失败", e);
return 0;
}
}
@Override
@Scheduled(fixedRate = 300000) // 每5分钟执行一次
public void cleanupExpiredSessions() {
try {
String pattern = SESSION_KEY_PREFIX + "*";
Set<String> sessionKeys = redisTemplate.keys(pattern);
if (sessionKeys != null) {
int cleanedCount = 0;
for (String sessionKey : sessionKeys) {
// Redis会自动清理过期的key,这里可以添加额外的清理逻辑
// 比如检查会话的最后活动时间等
cleanedCount++;
}
log.debug("会话清理完成,检查了 {} 个会话", cleanedCount);
}
} catch (Exception e) {
log.error("清理过期会话失败", e);
}
}
}
......@@ -11,7 +11,6 @@ import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 系统菜单Service实现类
......@@ -77,7 +76,7 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> impl
wrapper.eq(SysMenu::getDelFlag, "0");
// 排序:按父菜单ID和排序号升序
wrapper.orderByAsc(SysMenu::getParentId, SysMenu::getSort);
wrapper.orderByAsc(SysMenu::getParentId).orderByAsc(SysMenu::getSort);
return wrapper;
}
......
......@@ -198,33 +198,6 @@ public class DictValueConverter {
}
}
/**
* 加载指定字典类型到Redis
*/
private static void loadDictToRedis(String dictType) {
if (staticDictItemService == null) {
return;
}
try {
// 根据字典类型获取字典项
List<SysDictItem> dictItems = staticDictItemService.getDictItemsByType(dictType);
Map<String, String> mapping = new HashMap<>();
for (SysDictItem item : dictItems) {
if (item.getDelFlag() == null || "0".equals(item.getDelFlag())) {
mapping.put(item.getDictValue(), item.getDictLabel());
}
}
// 存储到Redis
String cacheKey = DICT_CACHE_PREFIX + dictType;
staticRedisTemplate.opsForValue().set(cacheKey, mapping, CACHE_EXPIRE_HOURS, TimeUnit.HOURS);
} catch (Exception e) {
System.err.println("加载字典到Redis失败: " + e.getMessage());
}
}
/**
* 根据字典类型ID获取字典类型编码
......
import { request } from '@/utils/request'
/**
* 首页统计数据响应接口
*/
export interface DashboardStats {
todayOrderCount: number
pendingDeliveryCount: number
pendingInvoiceCount: number
todaySalesAmount: number
onlineUserCount: number
systemStatus: string
systemVersion: string
orderGrowthRate: string
salesGrowthRate: string
}
export interface RecentActivity {
id: number
type: string
title: string
time: string
}
/**
* 首页API
*/
export const dashboardApi = {
/**
* 获取首页统计数据
*/
getDashboardStats: (): Promise<DashboardStats> => {
return request.get('/api/dashboard/stats')
},
/**
* 获取最近活动
*/
getRecentActivities: (): Promise<RecentActivity[]> => {
return request.get('/api/dashboard/activities')
},
/**
* 获取测试数据
*/
getTestStats: (): Promise<DashboardStats> => {
return request.get('/api/dashboard/test')
}
}
export default dashboardApi
......@@ -32,6 +32,8 @@ export interface RebateSearchParams {
productCode?: string
operateType?: number
calcFlag?: number
auditStatus?: number
dataSource?: string
rebateStartDate?: string
rebateEndDate?: string
}
......
This diff is collapsed. Click to expand it.
This diff is collapsed. Click to expand it.