# 短信登录开关与通用系统配置 Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 新建通用系统配置表和管理后台设置页面,通过「短信登录开关」控制小程序登录页是否显示手机号短信登录入口。 **Architecture:** 新建 `t_system_config` key-value 配置表,后端提供管理端 CRUD 接口和公开查询接口,管理后台新增「系统设置」页面动态渲染配置项,小程序登录页调用公开接口获取登录方式配置并条件渲染。 **Tech Stack:** Spring Boot 2.7 + MyBatis-Plus + Redis / Vue 3 + Element Plus / UniApp (Vue 3) **Spec:** `docs/superpowers/specs/2026-06-27-sms-login-config-design.md` --- ### Task 1: 数据库建表和初始数据 **Files:** - Execute: SQL on MySQL database - [ ] **Step 1: 执行建表 SQL** ```sql CREATE TABLE t_system_config ( id VARCHAR(64) PRIMARY KEY, config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置唯一标识', config_value VARCHAR(500) NOT NULL DEFAULT '' COMMENT '配置值', value_type VARCHAR(20) NOT NULL DEFAULT 'string' COMMENT '值类型: boolean/string/number/json', config_group VARCHAR(50) NOT NULL DEFAULT 'system' COMMENT '配置分组: system/login/notification', config_name VARCHAR(100) NOT NULL DEFAULT '' COMMENT '显示名称', description VARCHAR(300) DEFAULT '' COMMENT '配置说明', sort_order INT NOT NULL DEFAULT 0 COMMENT '排序号', is_visible TINYINT NOT NULL DEFAULT 1 COMMENT '是否在管理后台可见: 0-隐藏, 1-显示', create_by VARCHAR(64) DEFAULT NULL, create_time DATETIME DEFAULT NULL, update_by VARCHAR(64) DEFAULT NULL, update_time DATETIME DEFAULT NULL, is_deleted TINYINT NOT NULL DEFAULT 0 ) COMMENT = '系统配置表'; ``` - [ ] **Step 2: 插入初始数据(短信登录开关)** ```sql INSERT INTO t_system_config (id, config_key, config_value, value_type, config_group, config_name, description, sort_order, is_visible, create_time, update_time, is_deleted) VALUES ( REPLACE(UUID(), '-', ''), 'sms_login_enabled', 'false', 'boolean', 'login', '短信登录', '是否启用手机号+短信验证码登录方式', 1, 1, NOW(), NOW(), 0 ); ``` - [ ] **Step 3: 验证数据** ```sql SELECT * FROM t_system_config WHERE is_deleted = 0; ``` Expected: 一行数据,`config_key = 'sms_login_enabled'`,`config_value = 'false'` --- ### Task 2: 后端 Entity — SystemConfig.java **Files:** - Create: `server/src/main/java/com/emotion/entity/SystemConfig.java` - [ ] **Step 1: 创建 SystemConfig 实体** ```java package com.emotion.entity; import com.baomidou.mybatisplus.annotation.*; import com.emotion.common.BaseEntity; import lombok.experimental.SuperBuilder; import lombok.NoArgsConstructor; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; /** * 系统配置实体 * * @author system * @date 2026-06-27 */ @Data @EqualsAndHashCode(callSuper = true) @SuperBuilder @NoArgsConstructor @AllArgsConstructor @TableName("t_system_config") public class SystemConfig extends BaseEntity { /** * 配置唯一标识 */ @TableField("config_key") private String configKey; /** * 配置值 */ @TableField("config_value") private String configValue; /** * 值类型: boolean/string/number/json */ @TableField("value_type") private String valueType; /** * 配置分组: system/login/notification */ @TableField("config_group") private String configGroup; /** * 显示名称 */ @TableField("config_name") private String configName; /** * 配置说明 */ @TableField("description") private String description; /** * 排序号 */ @TableField("sort_order") private Integer sortOrder; /** * 是否在管理后台可见: 0-隐藏, 1-显示 */ @TableField("is_visible") private Integer isVisible; } ``` - [ ] **Step 2: Commit** ```bash git add server/src/main/java/com/emotion/entity/SystemConfig.java git commit -m "feat:新增系统配置实体类" ``` --- ### Task 3: 后端 Mapper — SystemConfigMapper.java **Files:** - Create: `server/src/main/java/com/emotion/mapper/SystemConfigMapper.java` - [ ] **Step 1: 创建 SystemConfigMapper** ```java package com.emotion.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.emotion.entity.SystemConfig; import org.apache.ibatis.annotations.Mapper; /** * 系统配置Mapper接口 * * @author system * @date 2026-06-27 */ @Mapper public interface SystemConfigMapper extends BaseMapper { } ``` - [ ] **Step 2: Commit** ```bash git add server/src/main/java/com/emotion/mapper/SystemConfigMapper.java git commit -m "feat:新增系统配置Mapper" ``` --- ### Task 4: 后端 DTOs **Files:** - Create: `server/src/main/java/com/emotion/dto/request/SystemConfigUpdateRequest.java` - Create: `server/src/main/java/com/emotion/dto/response/SystemConfigResponse.java` - Create: `server/src/main/java/com/emotion/dto/response/LoginConfigResponse.java` - [ ] **Step 1: 创建 SystemConfigUpdateRequest** ```java package com.emotion.dto.request; import lombok.Data; import javax.validation.constraints.NotBlank; /** * 系统配置更新请求 * * @author system * @date 2026-06-27 */ @Data public class SystemConfigUpdateRequest { /** * 配置唯一标识 */ @NotBlank(message = "配置标识不能为空") private String configKey; /** * 配置值 */ @NotBlank(message = "配置值不能为空") private String configValue; } ``` - [ ] **Step 2: 创建 SystemConfigResponse** ```java package com.emotion.dto.response; import lombok.Data; /** * 系统配置响应 * * @author system * @date 2026-06-27 */ @Data public class SystemConfigResponse { /** * 配置唯一标识 */ private String configKey; /** * 配置值 */ private String configValue; /** * 值类型: boolean/string/number/json */ private String valueType; /** * 配置分组 */ private String configGroup; /** * 显示名称 */ private String configName; /** * 配置说明 */ private String description; /** * 排序号 */ private Integer sortOrder; } ``` - [ ] **Step 3: 创建 LoginConfigResponse** ```java package com.emotion.dto.response; import lombok.Data; /** * 登录方式配置响应 * 返回给小程序,告知当前启用的登录方式 * * @author system * @date 2026-06-27 */ @Data public class LoginConfigResponse { /** * 是否启用微信登录 */ private Boolean wechatLoginEnabled; /** * 是否启用短信登录 */ private Boolean smsLoginEnabled; } ``` - [ ] **Step 4: Commit** ```bash git add server/src/main/java/com/emotion/dto/request/SystemConfigUpdateRequest.java git add server/src/main/java/com/emotion/dto/response/SystemConfigResponse.java git add server/src/main/java/com/emotion/dto/response/LoginConfigResponse.java git commit -m "feat:新增系统配置相关DTO" ``` --- ### Task 5: 后端 Service 接口 — SystemConfigService.java **Files:** - Create: `server/src/main/java/com/emotion/service/SystemConfigService.java` - [ ] **Step 1: 创建 SystemConfigService 接口** ```java package com.emotion.service; import com.baomidou.mybatisplus.extension.service.IService; import com.emotion.dto.request.SystemConfigUpdateRequest; import com.emotion.dto.response.LoginConfigResponse; import com.emotion.dto.response.SystemConfigResponse; import com.emotion.entity.SystemConfig; import java.util.List; /** * 系统配置服务接口 * * @author system * @date 2026-06-27 */ public interface SystemConfigService extends IService { /** * 获取所有可见配置列表 */ List getVisibleConfigList(); /** * 批量更新配置 */ void batchUpdateConfigs(List requests); /** * 获取配置值(带 Redis 缓存) * * @param configKey 配置标识 * @return 配置值字符串,不存在返回 null */ String getConfigValue(String configKey); /** * 获取登录方式配置 */ LoginConfigResponse getLoginConfig(); } ``` - [ ] **Step 2: Commit** ```bash git add server/src/main/java/com/emotion/service/SystemConfigService.java git commit -m "feat:新增系统配置服务接口" ``` --- ### Task 6: 后端 Service 实现 — SystemConfigServiceImpl.java **Files:** - Create: `server/src/main/java/com/emotion/service/impl/SystemConfigServiceImpl.java` - [ ] **Step 1: 创建 SystemConfigServiceImpl** ```java package com.emotion.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.emotion.dto.request.SystemConfigUpdateRequest; import com.emotion.dto.response.LoginConfigResponse; import com.emotion.dto.response.SystemConfigResponse; import com.emotion.entity.SystemConfig; import com.emotion.mapper.SystemConfigMapper; import com.emotion.service.SystemConfigService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; /** * 系统配置服务实现类 * * @author system * @date 2026-06-27 */ @Slf4j @Service public class SystemConfigServiceImpl extends ServiceImpl implements SystemConfigService { @Autowired private RedisTemplate redisTemplate; private static final String CACHE_PREFIX = "system_config:"; private static final long CACHE_EXPIRE_HOURS = 24; private static final String SMS_LOGIN_ENABLED_KEY = "sms_login_enabled"; @Override public List getVisibleConfigList() { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SystemConfig::getIsVisible, 1) .orderByAsc(SystemConfig::getSortOrder); List configs = list(wrapper); return configs.stream().map(this::convertToResponse).collect(Collectors.toList()); } @Override public void batchUpdateConfigs(List requests) { for (SystemConfigUpdateRequest request : requests) { LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(SystemConfig::getConfigKey, request.getConfigKey()) .set(SystemConfig::getConfigValue, request.getConfigValue()); update(updateWrapper); // 清除缓存 redisTemplate.delete(CACHE_PREFIX + request.getConfigKey()); log.info("更新系统配置: key={}, value={}", request.getConfigKey(), request.getConfigValue()); } } @Override public String getConfigValue(String configKey) { // 先查 Redis 缓存 String cacheKey = CACHE_PREFIX + configKey; String cached = (String) redisTemplate.opsForValue().get(cacheKey); if (cached != null) { return cached; } // 查数据库 LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SystemConfig::getConfigKey, configKey); SystemConfig config = getOne(wrapper); if (config == null) { return null; } // 写入缓存 redisTemplate.opsForValue().set(cacheKey, config.getConfigValue(), CACHE_EXPIRE_HOURS, TimeUnit.HOURS); return config.getConfigValue(); } @Override public LoginConfigResponse getLoginConfig() { LoginConfigResponse response = new LoginConfigResponse(); response.setWechatLoginEnabled(true); String smsEnabled = getConfigValue(SMS_LOGIN_ENABLED_KEY); response.setSmsLoginEnabled("true".equalsIgnoreCase(smsEnabled)); return response; } /** * 实体转响应对象 */ private SystemConfigResponse convertToResponse(SystemConfig config) { SystemConfigResponse response = new SystemConfigResponse(); response.setConfigKey(config.getConfigKey()); response.setConfigValue(config.getConfigValue()); response.setValueType(config.getValueType()); response.setConfigGroup(config.getConfigGroup()); response.setConfigName(config.getConfigName()); response.setDescription(config.getDescription()); response.setSortOrder(config.getSortOrder()); return response; } } ``` - [ ] **Step 2: Commit** ```bash git add server/src/main/java/com/emotion/service/impl/SystemConfigServiceImpl.java git commit -m "feat:新增系统配置服务实现(含Redis缓存)" ``` --- ### Task 7: 后端管理端 Controller — SystemConfigController.java **Files:** - Create: `server/src/main/java/com/emotion/controller/SystemConfigController.java` - [ ] **Step 1: 创建 SystemConfigController** 此 controller 的路径为 `/admin/systemConfig/**`,自动被 `AdminAuthInterceptor` 保护,不需要登录用户 JWT。 ```java package com.emotion.controller; import com.emotion.common.Result; import com.emotion.dto.request.SystemConfigUpdateRequest; import com.emotion.dto.response.SystemConfigResponse; import com.emotion.service.SystemConfigService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.util.List; /** * 系统配置管理控制器(管理端) * * @author system * @date 2026-06-27 */ @RestController @RequestMapping("/admin/systemConfig") @Tag(name = "系统配置管理", description = "系统配置的查询和更新功能") public class SystemConfigController { @Autowired private SystemConfigService systemConfigService; /** * 获取所有可见配置列表 */ @GetMapping("/list") @Operation(summary = "获取配置列表", description = "获取所有可见的系统配置列表") public Result> list() { List configs = systemConfigService.getVisibleConfigList(); return Result.success(configs); } /** * 批量更新配置 */ @PutMapping("/update") @Operation(summary = "批量更新配置", description = "批量更新系统配置值") public Result update(@RequestBody @Validated List requests) { systemConfigService.batchUpdateConfigs(requests); return Result.success("更新成功", null); } } ``` - [ ] **Step 2: Commit** ```bash git add server/src/main/java/com/emotion/controller/SystemConfigController.java git commit -m "feat:新增系统配置管理端Controller" ``` --- ### Task 8: 后端公开接口 — AuthController 新增 loginConfig **Files:** - Modify: `server/src/main/java/com/emotion/controller/AuthController.java` - [ ] **Step 1: 在 AuthController 中添加 loginConfig 接口** 在 `AuthController.java` 中添加 `SystemService` 注入和新接口方法。 添加 import: ```java import com.emotion.dto.response.LoginConfigResponse; ``` 添加 service 注入(在已有的 `@Autowired` 字段后面): ```java @Autowired private SystemConfigService systemConfigService; ``` 添加新接口方法(放在 `wechatLogin` 方法后面): ```java /** * 获取登录方式配置 */ @GetMapping("/loginConfig") @Operation(summary = "获取登录方式配置", description = "返回当前启用的登录方式,供小程序登录页动态渲染") public Result getLoginConfig() { LoginConfigResponse response = systemConfigService.getLoginConfig(); return Result.success(response); } ``` - [ ] **Step 2: 在 WebMvcConfig 白名单中放行** 修改 `server/src/main/java/com/emotion/config/WebMvcConfig.java`,在 JWT 拦截器的 `excludePathPatterns` 中添加一行: ```java "/auth/loginConfig", // 登录方式配置接口(免登录) ``` 添加位置:在 `"/auth/wechat/login"` 行的上方或下方。 - [ ] **Step 3: Commit** ```bash git add server/src/main/java/com/emotion/controller/AuthController.java git add server/src/main/java/com/emotion/config/WebMvcConfig.java git commit -m "feat:新增公开接口 /auth/loginConfig 并放行白名单" ``` --- ### Task 9: 后端编译验证 - [ ] **Step 1: 编译后端** ```bash cd server mvn clean install -DskipTests ``` Expected: `BUILD SUCCESS` - [ ] **Step 2: 如果有编译错误,修复后重新编译直到通过** - [ ] **Step 3: Commit(如果有修复)** ```bash git add -A git commit -m "fix:修复系统配置编译错误" ``` --- ### Task 10: 管理后台 — API 和路由配置 **Files:** - Create: `web-admin/src/api/systemConfig.ts` - Modify: `web-admin/src/router/index.ts` - Modify: `web-admin/src/config/menu.ts` - [ ] **Step 1: 创建 systemConfig API 文件** `web-admin/src/api/systemConfig.ts`: ```ts import request from '@/utils/request' // 获取所有可见配置列表 export function getSystemConfigList() { return request({ url: '/admin/systemConfig/list', method: 'get' }) } // 批量更新配置 export function updateSystemConfig(data: { configKey: string; configValue: string }[]) { return request({ url: '/admin/systemConfig/update', method: 'put', data }) } ``` - [ ] **Step 2: 添加路由配置** 在 `web-admin/src/router/index.ts` 中,在 `dictionary` 路由块之前添加: ```ts { path: '/system', component: Layout, redirect: '/system/settings', meta: { title: '系统设置', icon: 'Setting' }, children: [ { path: 'settings', name: 'SystemSettings', component: () => import('@/views/system/SystemSettings.vue'), meta: { title: '基础设置' } } ] }, ``` - [ ] **Step 3: 添加菜单配置** 在 `web-admin/src/config/menu.ts` 的 `menuConfig` 数组中,在 `AI配置管理` 项之前添加: ```ts { path: '/system', title: '系统设置', icon: 'Setting' }, ``` - [ ] **Step 4: Commit** ```bash git add web-admin/src/api/systemConfig.ts git add web-admin/src/router/index.ts git add web-admin/src/config/menu.ts git commit -m "feat:管理后台新增系统设置路由和菜单" ``` --- ### Task 11: 管理后台 — SystemSettings.vue 页面 **Files:** - Create: `web-admin/src/views/system/SystemSettings.vue` - [ ] **Step 1: 创建 SystemSettings 页面** ```vue ``` - [ ] **Step 2: Commit** ```bash git add web-admin/src/views/system/SystemSettings.vue git commit -m "feat:管理后台新增系统设置页面" ``` --- ### Task 12: 小程序 — 新增 getLoginConfig 接口 **Files:** - Modify: `mini-program/src/services/auth.js` - [ ] **Step 1: 在 auth.js 中添加 getLoginConfig 方法** 在 `mini-program/src/services/auth.js` 的 `getSmsCode` 方法之前,添加: ```js /** * 获取登录方式配置 * @returns {Promise} 登录方式配置 */ export const getLoginConfig = async () => { const response = await get('/auth/loginConfig') return response } ``` - [ ] **Step 2: 在底部的 default 导出对象中添加 `getLoginConfig`** ```js export default { getLoginConfig, getSmsCode, login, wechatLogin, logout, refreshToken, validateToken, validateTokenStrict, getCurrentUserInfo, checkPhone } ``` - [ ] **Step 3: Commit** ```bash git add mini-program/src/services/auth.js git commit -m "feat:小程序新增获取登录方式配置接口" ``` --- ### Task 13: 小程序 — 登录页条件渲染 **Files:** - Modify: `mini-program/src/pages/login/index.vue` - [ ] **Step 1: 修改 template — 用 v-if 控制 SMS 相关元素** 在 `mini-program/src/pages/login/index.vue` 中: 1. 在「微信一键登录」按钮的 `` 之后,「分隔线」的 `