Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"permissions": {
"allow": [
"Bash(git:*)"
]
}
}
37 changes: 37 additions & 0 deletions .idea/dataSources.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ docker compose up -d --build
### 3) 初始化数据库(首次部署建议执行)

```bash
docker compose exec -T mysql mysql -uroot -proot smartlab < sql/schema.sql
docker compose exec -T mysql mysql -uroot -proot smartlab < sql/seed.sql
docker compose exec -T mysql mysql --default-character-set=utf8mb4 -uroot -proot smartlab < sql/schema.sql
docker compose exec -T mysql mysql --default-character-set=utf8mb4 -uroot -proot smartlab < sql/seed.sql
```

### 4) 常用运维命令
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ services:
DB_PASSWORD: root
JWT_SECRET: smartlab-prod-jwt-secret-change-me-32bytes
JWT_EXPIRE_HOURS: 24
CORS_ALLOWED_ORIGIN_PATTERNS: http://127.0.0.1:5173,http://localhost:5173
ports:
- "8080:8080"

Expand All @@ -51,4 +52,3 @@ services:

volumes:
mysql_data:

210 changes: 210 additions & 0 deletions docs/debug.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# SmartLab Debug Notes

日期:2026-03-17

## 当前结论

目前后端登录接口和前端代理链路都已经验证正常。

已确认:

- `mysql`、`server`、`web` 三个容器都在运行
- `schema.sql` 已导入成功,业务表已存在
- `seed.sql` 已至少成功导入到能查出 `sys_user` 和 `sys_role`
- `sys_user` 中已有:
- `admin`, `status = 1`, `role_id = 1`
- `teacher`, `status = 1`, `role_id = 2`
- `student`, `status = 1`, `role_id = 3`
- 直接请求后端登录接口成功:
- `POST http://localhost:8080/api/auth/login`
- 通过前端代理请求登录接口也成功:
- `POST http://localhost:5173/api/auth/login`

这说明以下链路没有问题:

- MySQL 基本可用
- 后端 `/api/auth/login` 可用
- 前端 Nginx 代理 `/api/*` 到后端可用

## 已确认的命令结果

### 1. 容器状态

`docker compose ps` 显示:

- `smartlab-mysql` 正常运行
- `smartlab-server` 正常运行
- `smartlab-web` 正常运行

### 2. 表结构已存在

执行:

```powershell
docker compose exec -T mysql mysql -uroot -proot -e "USE smartlab; SHOW TABLES;"
```

已看到这些表:

- `equipment`
- `equipment_category`
- `lab_room`
- `maintenance_order`
- `notice`
- `reservation`
- `reservation_item`
- `sys_permission`
- `sys_role`
- `sys_role_permission`
- `sys_user`

### 3. 用户数据已存在

执行:

```powershell
docker compose exec -T mysql mysql -uroot -proot -e "USE smartlab; SELECT id, username, status, role_id FROM sys_user;"
```

结果为:

```text
1 admin 1 1
2 teacher 1 2
3 student 1 3
```

### 4. 后端登录接口成功

执行:

```powershell
Invoke-RestMethod -Method Post -Uri "http://localhost:8080/api/auth/login" -ContentType "application/json" -Body '{"username":"admin","password":"123456"}'
```

返回:

- `code = 0`
- `message = success`
- `data.token` 存在

### 5. 前端代理登录接口成功

执行:

```powershell
Invoke-RestMethod -Method Post -Uri "http://localhost:5173/api/auth/login" -ContentType "application/json" -Body '{"username":"admin","password":"123456"}'
```

返回:

- `code = 0`
- `message = success`
- `data.token` 存在

## 之前出现过的问题

### 1. PowerShell 命令经常因手误失败

出现过这些拼写问题:

- `-uroot` 被写成 `-urot`
- `-proot` 被写成 `-proof`
- `-uroot` 被拆成单独一行执行
- `SHOW TABLES;` 被写成 `SHOW TABLES:`
- `Invoke-RestMethod -Uri` 后漏空格

这些都不是项目问题。

### 2. PowerShell 管道导入 `seed.sql` 存在编码问题

执行过:

```powershell
Get-Content .\sql\seed.sql | docker compose exec -T mysql mysql -uroot -proot smartlab
```

出现过中文内容变成 `????` 的情况,并导致 SQL 语法错误。

这说明在 PowerShell 下,用 `Get-Content | docker compose exec ...` 导入含中文的 `seed.sql` 不可靠。

### 3. 浏览器里曾出现旧 token 导致的 `/api/auth/me` 401

Web 容器日志中看到:

- 页面加载时曾直接请求 `GET /api/auth/me`
- 该请求返回 `401`

这很可能是浏览器本地存储里残留了旧 token。

## 下次开机后优先做的事

### 1. 先确认容器仍然正常

```powershell
docker compose ps
```

### 2. 直接验证登录接口和 `/auth/me`

先拿 token:

```powershell
$login = Invoke-RestMethod -Method Post -Uri "http://localhost:5173/api/auth/login" -ContentType "application/json" -Body '{"username":"admin","password":"123456"}'
$token = $login.data.token
```

再验证当前用户接口:

```powershell
Invoke-RestMethod -Method Get -Uri "http://localhost:5173/api/auth/me" -Headers @{ Authorization = "Bearer $token" }
```

需要确认:

- 这条是否成功返回当前用户信息
- 如果失败,失败码是不是 `401`

### 3. 清浏览器本地 token

在浏览器打开 `http://localhost:5173/login`,按 `F12`,在 `Console` 执行:

```js
localStorage.clear()
location.reload()
```

然后重新用:

- 用户名:`admin`
- 密码:`123456`

### 4. 若页面仍失败,重点抓两条请求

浏览器开发者工具 `Network` 中只看:

- `POST /api/auth/login`
- `GET /api/auth/me`

需要记录:

- 状态码
- 响应体

## 建议的后续修复方向

建议后续做两件事:

1. 调整数据库初始化方式,避免手工导入 SQL
2. 优化前端登录错误提示

原因:

- 当前初始化过度依赖手工命令,PowerShell 下很容易踩编码和重定向问题
- 前端现在会把登录成功后 `/auth/me` 的失败笼统显示成“登录服务暂时不可用”,定位不够直接

## 文档位置

本次记录文件:

- [docs/debug.md](/mnt/d/smartlab/docs/debug.md)
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
package com.smartlab.server.common.config;

import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.incrementer.DefaultIdentifierGenerator;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

Expand All @@ -14,17 +17,34 @@
@Configuration
public class MybatisConfig {

@Value("${smartlab.id.worker-id:1}")
private long workerId;

@Value("${smartlab.id.datacenter-id:1}")
private long datacenterId;

@Bean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setPlugins(mybatisPlusInterceptor());
factoryBean.setGlobalConfig(globalConfig());
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setMapUnderscoreToCamelCase(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();
}

@Bean
public GlobalConfig globalConfig() {
GlobalConfig.Sequence sequence = new GlobalConfig.Sequence()
.setWorkerId(workerId)
.setDatacenterId(datacenterId);
return new GlobalConfig()
.setSequence(sequence)
.setIdentifierGenerator(new DefaultIdentifierGenerator(workerId, datacenterId));
}

@Bean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.smartlab.server.common.exception.BusinessException;
import com.smartlab.server.modules.auth.dto.LoginRequest;
import com.smartlab.server.modules.auth.model.AuthUserInfo;
import com.smartlab.server.modules.auth.support.AuthStatusSupport;
import com.smartlab.server.modules.auth.service.AuthService;
import com.smartlab.server.modules.auth.vo.CurrentUserVO;
import com.smartlab.server.modules.auth.vo.LoginResponse;
Expand Down Expand Up @@ -33,7 +34,7 @@ public LoginResponse login(LoginRequest request) {
if (userInfo == null || !passwordEncoder.matches(request.getPassword(), userInfo.getPassword())) {
throw new BusinessException(401, "用户名或密码错误");
}
if (userInfo.getStatus() == null || userInfo.getStatus() != 1) {
if (!AuthStatusSupport.isUserEnabled(userInfo.getStatus())) {
throw new BusinessException(403, "用户已被禁用");
}
String token = jwtTokenProvider.generateToken(userInfo.getUserId(), userInfo.getUsername(), userInfo.getRoleCode());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.smartlab.server.modules.auth.support;

public final class AuthStatusSupport {

private AuthStatusSupport() {
}

public static boolean isUserEnabled(Integer status) {
return status != null && status == 1;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ public DashboardSummaryVO summary() {
long todayReservationCount = reservationMapper.selectCount(
new LambdaQueryWrapper<Reservation>().eq(Reservation::getUseDate, today)
);
long pendingApprovalCount = reservationMapper.selectCount(
new LambdaQueryWrapper<Reservation>().eq(Reservation::getStatus, "PENDING")
);
long processingMaintenanceCount = maintenanceOrderMapper.selectCount(
new LambdaQueryWrapper<MaintenanceOrder>().ne(MaintenanceOrder::getStatus, "FINISHED")
);
Expand All @@ -60,6 +63,7 @@ public DashboardSummaryVO summary() {
.collect(Collectors.groupingBy(Equipment::getStatus, Collectors.counting()));
DashboardSummaryVO vo = new DashboardSummaryVO();
vo.setTodayReservationCount(todayReservationCount);
vo.setPendingApprovalCount(pendingApprovalCount);
vo.setProcessingMaintenanceCount(processingMaintenanceCount);
vo.setPublishedNoticeCount(publishedNoticeCount);
vo.setEquipmentStatusDistribution(statusDistribution);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public class DashboardRankVO {
private Long equipmentId;
private String assetNo;
private String name;
private String labName;
private Long reservationCount;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
public class DashboardSummaryVO {

private long todayReservationCount;
private long pendingApprovalCount;
private long processingMaintenanceCount;
private long publishedNoticeCount;
private Map<String, Long> equipmentStatusDistribution;
Expand Down
Loading
Loading