fix: 修复后端兼容性和前后端配置问题

后端修复:
- Spring Boot 降级到 3.1.12 以兼容 MyBatis-Plus 3.5.6
- 添加 RedisConfig 配置 RedisTemplate Bean
- 修复数据库连接字符编码 characterEncoding=UTF-8
- 添加健康检查接口 /api/v1/health 到认证白名单
- 实体字段同步数据库: WorkLog 添加 recordTime, LogTemplate 添加 templateContent/instruction
- 修复 logback 滚动策略配置
- 密码验证临时改为明文比对(测试用)

前端修复:
- API baseURL 统一修正为 /wlog/api/v1
- Vite 配置添加 base 路径 (/wladmin/, /wlmobile/)

脚本修复:
- stop.sh/status.sh 使用动态 APP_HOME 获取路径
This commit is contained in:
zhangjf 2026-02-24 22:47:14 +08:00
parent ba9e2c12c8
commit e36ac36af5
26 changed files with 105 additions and 59 deletions

View File

@ -3,9 +3,12 @@
# 工作日志服务平台 - 应用状态查看脚本 # 工作日志服务平台 - 应用状态查看脚本
# ==================================================== # ====================================================
# 获取脚本所在目录的上级目录作为应用根目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_HOME="$(cd "${SCRIPT_DIR}/.." && pwd)"
# 应用配置 # 应用配置
APP_NAME="worklog-api" APP_NAME="worklog-api"
APP_HOME="/opt/worklog/${APP_NAME}"
PID_FILE="${APP_HOME}/${APP_NAME}.pid" PID_FILE="${APP_HOME}/${APP_NAME}.pid"
# 颜色输出 # 颜色输出

View File

@ -3,9 +3,12 @@
# 工作日志服务平台 - 应用停止脚本 # 工作日志服务平台 - 应用停止脚本
# ==================================================== # ====================================================
# 获取脚本所在目录的上级目录作为应用根目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
APP_HOME="$(cd "${SCRIPT_DIR}/.." && pwd)"
# 应用配置 # 应用配置
APP_NAME="worklog-api" APP_NAME="worklog-api"
APP_HOME="/opt/worklog/${APP_NAME}"
PID_FILE="${APP_HOME}/${APP_NAME}.pid" PID_FILE="${APP_HOME}/${APP_NAME}.pid"
# 检查 PID 文件是否存在 # 检查 PID 文件是否存在

View File

@ -8,7 +8,7 @@
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.2</version> <version>3.1.12</version>
<relativePath/> <relativePath/>
</parent> </parent>
@ -22,7 +22,7 @@
<java.version>21</java.version> <java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<mybatis-plus.version>3.5.5</mybatis-plus.version> <mybatis-plus.version>3.5.6</mybatis-plus.version>
<hutool.version>5.8.25</hutool.version> <hutool.version>5.8.25</hutool.version>
<springdoc.version>2.3.0</springdoc.version> <springdoc.version>2.3.0</springdoc.version>
<mysql.version>8.0.33</mysql.version> <mysql.version>8.0.33</mysql.version>

View File

@ -21,13 +21,14 @@ public class PasswordUtil {
} }
/** /**
* 校验密码 * 校验密码临时明文比对
* *
* @param rawPassword 明文密码 * @param rawPassword 明文密码
* @param encodedPassword 加密后的密码 * @param encodedPassword 加密后的密码
* @return 是否匹配 * @return 是否匹配
*/ */
public static boolean matches(String rawPassword, String encodedPassword) { public static boolean matches(String rawPassword, String encodedPassword) {
return ENCODER.matches(rawPassword, encodedPassword); // TODO: 临时使用明文比对生产环境需恢复 BCrypt 验证
return rawPassword.equals(encodedPassword);
} }
} }

View File

@ -0,0 +1,33 @@
package com.wjbl.worklog.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 配置类
*/
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
// Key 使用 String 序列化
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 使用 JSON 序列化
GenericJackson2JsonRedisSerializer jsonSerializer = new GenericJackson2JsonRedisSerializer();
template.setValueSerializer(jsonSerializer);
template.setHashValueSerializer(jsonSerializer);
template.afterPropertiesSet();
return template;
}
}

View File

@ -24,7 +24,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
private static final String[] WHITE_LIST = { private static final String[] WHITE_LIST = {
"/api/v1/auth/login", "/api/v1/auth/login",
"/api/v1/auth/logout", "/api/v1/auth/logout",
"/api/v1/auth/health", "/api/v1/health",
"/swagger-ui.html", "/swagger-ui.html",
"/swagger-ui/**", "/swagger-ui/**",
"/v3/api-docs/**", "/v3/api-docs/**",

View File

@ -30,7 +30,12 @@ public class LogTemplate implements Serializable {
/** /**
* 模板内容Markdown格式 * 模板内容Markdown格式
*/ */
private String content; private String templateContent;
/**
* 使用说明
*/
private String instruction;
/** /**
* 状态0-禁用1-启用 * 状态0-禁用1-启用

View File

@ -34,9 +34,9 @@ public class WorkLog implements Serializable {
private LocalDate logDate; private LocalDate logDate;
/** /**
* 日志标题 * 记录时间
*/ */
private String title; private LocalDateTime recordTime;
/** /**
* 日志内容Markdown格式 * 日志内容Markdown格式

View File

@ -22,13 +22,6 @@ public class LogCreateDTO implements Serializable {
@Schema(description = "日志日期,默认当天") @Schema(description = "日志日期,默认当天")
private LocalDate logDate; private LocalDate logDate;
/**
* 日志标题
*/
@NotBlank(message = "标题不能为空")
@Schema(description = "日志标题", requiredMode = Schema.RequiredMode.REQUIRED)
private String title;
/** /**
* 日志内容Markdown格式 * 日志内容Markdown格式
*/ */

View File

@ -14,12 +14,6 @@ public class LogUpdateDTO implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
/**
* 日志标题
*/
@Schema(description = "日志标题")
private String title;
/** /**
* 日志内容Markdown格式 * 日志内容Markdown格式
*/ */

View File

@ -27,4 +27,10 @@ public class TemplateCreateDTO implements Serializable {
*/ */
@Schema(description = "模板内容Markdown格式") @Schema(description = "模板内容Markdown格式")
private String content; private String content;
/**
* 使用说明
*/
@Schema(description = "使用说明")
private String instruction;
} }

View File

@ -25,4 +25,10 @@ public class TemplateUpdateDTO implements Serializable {
*/ */
@Schema(description = "模板内容Markdown格式") @Schema(description = "模板内容Markdown格式")
private String content; private String content;
/**
* 使用说明
*/
@Schema(description = "使用说明")
private String instruction;
} }

View File

@ -42,7 +42,7 @@ public class LogServiceImpl implements LogService {
WorkLog workLog = new WorkLog(); WorkLog workLog = new WorkLog();
workLog.setUserId(currentUserId); workLog.setUserId(currentUserId);
workLog.setLogDate(logDate); workLog.setLogDate(logDate);
workLog.setTitle(dto.getTitle()); workLog.setRecordTime(LocalDateTime.now());
workLog.setContent(dto.getContent()); workLog.setContent(dto.getContent());
workLog.setTemplateId(dto.getTemplateId()); workLog.setTemplateId(dto.getTemplateId());
workLog.setDeleted(0); workLog.setDeleted(0);
@ -77,9 +77,6 @@ public class LogServiceImpl implements LogService {
} }
// 更新日志 // 更新日志
if (dto.getTitle() != null) {
workLog.setTitle(dto.getTitle());
}
if (dto.getContent() != null) { if (dto.getContent() != null) {
workLog.setContent(dto.getContent()); workLog.setContent(dto.getContent());
} }

View File

@ -37,7 +37,8 @@ public class TemplateServiceImpl implements TemplateService {
// 创建模板 // 创建模板
LogTemplate template = new LogTemplate(); LogTemplate template = new LogTemplate();
template.setTemplateName(dto.getTemplateName()); template.setTemplateName(dto.getTemplateName());
template.setContent(dto.getContent()); template.setTemplateContent(dto.getContent());
template.setInstruction(dto.getInstruction());
template.setStatus(1); // 默认启用 template.setStatus(1); // 默认启用
template.setDeleted(0); template.setDeleted(0);
@ -74,7 +75,10 @@ public class TemplateServiceImpl implements TemplateService {
// 更新模板内容 // 更新模板内容
if (dto.getContent() != null) { if (dto.getContent() != null) {
template.setContent(dto.getContent()); template.setTemplateContent(dto.getContent());
}
if (dto.getInstruction() != null) {
template.setInstruction(dto.getInstruction());
} }
// 设置审计字段 // 设置审计字段
@ -142,7 +146,13 @@ public class TemplateServiceImpl implements TemplateService {
*/ */
private TemplateVO convertToVO(LogTemplate template) { private TemplateVO convertToVO(LogTemplate template) {
TemplateVO vo = new TemplateVO(); TemplateVO vo = new TemplateVO();
BeanUtils.copyProperties(template, vo); vo.setId(template.getId());
vo.setTemplateName(template.getTemplateName());
vo.setContent(template.getTemplateContent());
vo.setInstruction(template.getInstruction());
vo.setStatus(template.getStatus());
vo.setCreatedTime(template.getCreatedTime());
vo.setUpdatedTime(template.getUpdatedTime());
return vo; return vo;
} }
} }

View File

@ -34,12 +34,6 @@ public class LogVO implements Serializable {
@Schema(description = "日志日期") @Schema(description = "日志日期")
private LocalDate logDate; private LocalDate logDate;
/**
* 日志标题
*/
@Schema(description = "日志标题")
private String title;
/** /**
* 日志内容Markdown格式 * 日志内容Markdown格式
*/ */

View File

@ -33,6 +33,12 @@ public class TemplateVO implements Serializable {
@Schema(description = "模板内容Markdown格式") @Schema(description = "模板内容Markdown格式")
private String content; private String content;
/**
* 使用说明
*/
@Schema(description = "使用说明")
private String instruction;
/** /**
* 状态0-禁用1-启用 * 状态0-禁用1-启用
*/ */

View File

@ -22,7 +22,7 @@ spring:
# 数据源配置 # 数据源配置
datasource: datasource:
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/worklog?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true url: jdbc:mysql://localhost:3306/worklog?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: worklog username: worklog
password: Wlog@123 password: Wlog@123
type: com.zaxxer.hikari.HikariDataSource type: com.zaxxer.hikari.HikariDataSource

View File

@ -24,11 +24,9 @@
<pattern>${LOG_PATTERN}</pattern> <pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <charset>UTF-8</charset>
</encoder> </encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <fileNamePattern>${LOG_PATH}/app-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize>
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory> <maxHistory>30</maxHistory>
</rollingPolicy> </rollingPolicy>
</appender> </appender>
@ -40,11 +38,9 @@
<pattern>${LOG_PATTERN}</pattern> <pattern>${LOG_PATTERN}</pattern>
<charset>UTF-8</charset> <charset>UTF-8</charset>
</encoder> </encoder>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOG_PATH}/sql-%d{yyyy-MM-dd}.%i.log</fileNamePattern> <fileNamePattern>${LOG_PATH}/sql-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP"> <maxFileSize>100MB</maxFileSize>
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<maxHistory>30</maxHistory> <maxHistory>30</maxHistory>
</rollingPolicy> </rollingPolicy>
</appender> </appender>

View File

@ -43,7 +43,6 @@ class LogServiceTest {
testLog.setId("log-id-123"); testLog.setId("log-id-123");
testLog.setUserId("user-id-123"); testLog.setUserId("user-id-123");
testLog.setLogDate(LocalDate.now()); testLog.setLogDate(LocalDate.now());
testLog.setTitle("今日工作日志");
testLog.setContent("今天完成了xxx任务"); testLog.setContent("今天完成了xxx任务");
testLog.setDeleted(0); testLog.setDeleted(0);
@ -57,7 +56,6 @@ class LogServiceTest {
void createLog_success() { void createLog_success() {
// Given // Given
LogCreateDTO dto = new LogCreateDTO(); LogCreateDTO dto = new LogCreateDTO();
dto.setTitle("新日志");
dto.setContent("日志内容"); dto.setContent("日志内容");
dto.setLogDate(LocalDate.now()); dto.setLogDate(LocalDate.now());
@ -69,7 +67,7 @@ class LogServiceTest {
// Then // Then
assertNotNull(result); assertNotNull(result);
assertEquals("日志", result.getTitle()); assertEquals("日志内容", result.getContent());
verify(workLogDataService).save(any(WorkLog.class)); verify(workLogDataService).save(any(WorkLog.class));
} }
@ -78,7 +76,6 @@ class LogServiceTest {
void createLog_alreadyExists() { void createLog_alreadyExists() {
// Given // Given
LogCreateDTO dto = new LogCreateDTO(); LogCreateDTO dto = new LogCreateDTO();
dto.setTitle("新日志");
dto.setContent("日志内容"); dto.setContent("日志内容");
dto.setLogDate(LocalDate.now()); dto.setLogDate(LocalDate.now());
@ -94,7 +91,6 @@ class LogServiceTest {
void updateLog_success_ownLog() { void updateLog_success_ownLog() {
// Given // Given
LogUpdateDTO dto = new LogUpdateDTO(); LogUpdateDTO dto = new LogUpdateDTO();
dto.setTitle("更新后的标题");
dto.setContent("更新后的内容"); dto.setContent("更新后的内容");
when(workLogDataService.getById("log-id-123")).thenReturn(testLog); when(workLogDataService.getById("log-id-123")).thenReturn(testLog);
@ -105,7 +101,7 @@ class LogServiceTest {
// Then // Then
assertNotNull(result); assertNotNull(result);
assertEquals("更新后的标题", result.getTitle()); assertEquals("更新后的内容", result.getContent());
} }
@Test @Test
@ -118,7 +114,7 @@ class LogServiceTest {
otherUserLog.setLogDate(LocalDate.now()); otherUserLog.setLogDate(LocalDate.now());
LogUpdateDTO dto = new LogUpdateDTO(); LogUpdateDTO dto = new LogUpdateDTO();
dto.setTitle("更新后的标题"); dto.setContent("更新后的内容");
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog); when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
@ -140,7 +136,7 @@ class LogServiceTest {
otherUserLog.setLogDate(LocalDate.now()); otherUserLog.setLogDate(LocalDate.now());
LogUpdateDTO dto = new LogUpdateDTO(); LogUpdateDTO dto = new LogUpdateDTO();
dto.setTitle("管理员更新"); dto.setContent("管理员更新内容");
when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog); when(workLogDataService.getById("other-log-id")).thenReturn(otherUserLog);
when(workLogDataService.updateById(any(WorkLog.class))).thenReturn(true); when(workLogDataService.updateById(any(WorkLog.class))).thenReturn(true);
@ -150,7 +146,7 @@ class LogServiceTest {
// Then // Then
assertNotNull(result); assertNotNull(result);
assertEquals("管理员更新", result.getTitle()); assertEquals("管理员更新内容", result.getContent());
} }
@Test @Test
@ -193,7 +189,7 @@ class LogServiceTest {
// Then // Then
assertNotNull(result); assertNotNull(result);
assertEquals("日工作日志", result.getTitle()); assertEquals("天完成了xxx任务", result.getContent());
} }
@Test @Test

View File

@ -42,7 +42,7 @@ class TemplateServiceTest {
testTemplate = new LogTemplate(); testTemplate = new LogTemplate();
testTemplate.setId("template-id-123"); testTemplate.setId("template-id-123");
testTemplate.setTemplateName("日报模板"); testTemplate.setTemplateName("日报模板");
testTemplate.setContent("# 今日工作\n\n## 完成事项\n\n## 明日计划"); testTemplate.setTemplateContent("# 今日工作\n\n## 完成事项\n\n## 明日计划");
testTemplate.setStatus(1); testTemplate.setStatus(1);
testTemplate.setDeleted(0); testTemplate.setDeleted(0);

View File

@ -5,7 +5,7 @@ import { TOKEN_KEY } from '@/utils/constants'
// 创建 axios 实例 // 创建 axios 实例
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: '/wlmobile/api/v1', baseURL: '/wlog/api/v1',
timeout: 15000 timeout: 15000
}) })

File diff suppressed because one or more lines are too long

View File

@ -3,6 +3,7 @@ import vue from '@vitejs/plugin-vue';
import { fileURLToPath, URL } from 'node:url'; import { fileURLToPath, URL } from 'node:url';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: '/wlmobile/',
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {

View File

@ -4,6 +4,7 @@ import { fileURLToPath, URL } from 'node:url'
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
base: '/wlmobile/',
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {

View File

@ -5,7 +5,7 @@ import { TOKEN_KEY } from '@/utils/constants'
// 创建 axios 实例 // 创建 axios 实例
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: '/wladmin/api/v1', baseURL: '/wlog/api/v1',
timeout: 15000 timeout: 15000
}) })

View File

@ -4,6 +4,7 @@ import path from 'path'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
base: '/wladmin/',
plugins: [vue()], plugins: [vue()],
resolve: { resolve: {
alias: { alias: {