乐于分享
好东西不私藏

AI时代必须会的全栈入门:Vue3 + SpringBoot 从0实现登录功能

AI时代必须会的全栈入门:Vue3 + SpringBoot 从0实现登录功能

前几天有个粉丝跟我说:“现在AI影响太大了,公司全面要求开始搞全栈。前端的一起写后端,后端的一起写前端。虽然AI能生成代码,可一旦报错或者需要微调的时候,实在是太难受了”。

AI是放大器,但它不能替代你的基本功,所以今天我就基于 Vue3 + SpringBoot 实现一个注册登录的前后端分离的项目。

一、功能需求描述(注册 + 登录)

在正式开始写代码之前,我们先明确一下这次要实现的完整功能。我们要做的是一个完整的用户注册与登录模块,这也是绝大多数 Web 应用的入口功能。

前后端交互流程

【注册流程】┌─────────┐          ┌─────────┐          ┌─────────┐│  前端   │          │  后端   │          │ 数据库  │└────┬────┘          └────┬────┘          └────┬────┘     │                    │                    │     │ ① 输入用户名失焦    │                    │     │──校验用户名────────>│                    │     │<──是否可用──────────│                    │     │                    │                    │     │ ② POST /api/auth/register              │     │───────────────────>│                    │     │                    │ ③ 校验用户名唯一    │     │                    │───────────────────>│     │                    │                    │     │                    │ ④ 密码加密存库      │     │                    │───────────────────>│     │                    │                    │     │ ⑤ 注册成功/失败     │                    │     │<───────────────────│                    │     │                    │                    │     │ ⑥ 自动跳转登录页    │                    │
【登录流程】┌─────────┐          ┌─────────┐          ┌─────────┐│  前端   │          │  后端   │          │ 数据库  │└────┬────┘          └────┬────┘          └────┬────┘     │                    │                    │     │ ① POST /api/auth/login                 │     │───────────────────>│                    │     │                    │ ② 查询用户          │     │                    │───────────────────>│     │                    │<───────────────────│     │                    │                    │     │                    │ ③ 验证密码+状态     │     │                    │                    │     │                    │ ④ 生成 JWT Token   │     │                    │                    │     │ ⑤ 返回Token+用户信息│                    │     │<───────────────────│                    │     │                    │                    │     │ ⑥ localStorage存储  │                    │     │                    │                    │     │ ⑦ 跳转首页          │                    │

麻雀虽小但五脏俱全,通过这个功能你可以掌握前后端分离开发的核心流程。接下来直接跟着代码复制粘贴就可以了。

项目效果图:

二、环境前置条件

在开始之前,你的电脑至少要准备这些环境和工具:

  • • Node.js:版本 16.0 或更高(用于运行 Vue3 项目)
  • • JDK 17:用于运行 SpringBoot 项目
  • • MySQL:版本 5.7 或 8.0(作为数据库)
  • • Maven:版本 3.6 或更高(用于管理 SpringBoot 依赖)
  • • IDE:推荐 IntelliJ IDEA(后端)和 VS Code(前端)
  • • 接口测试工具:Postman 或 Apifox
  • • 数据库管理工具:Navicat 或者 Dbeaver

三、技术选型说明

后端技术栈

技术
版本
说明
SpringBoot
3.x
基础框架
JDK
17
Java 运行环境
MyBatis-Plus
3.5.x
ORM 框架
MySQL
8.0
关系型数据库
Lombok
最新
简化代码
JWT
0.11.x
身份认证
Spring Security
6.x
安全框架

前端技术栈

技术
版本
说明
Vue
3.x
前端框架
Vite
4.x
构建工具
Element Plus
2.x
UI 组件库
Axios
1.x
HTTP 请求库
Vue Router
4.x
路由管理

四、数据库设计

4.1 创建数据库

可以在工具新建

也可以直接执行下面这条sql。

CREATE DATABASE IF NOT EXISTS vue3_springboot_demoDEFAULT CHARACTER SET utf8mb4COLLATE utf8mb4_unicode_ci;

意思是创建一个数据库,名字叫 vue3_springboot_demo,并使用 utf8mb4 字符编码(支持中文和表情),utf8mb4_unicode_ci 排序规则(不区分大小写、排序更准确)。

这里分版本,如果是 MySQL 5.7建议用 utf8mb4_unicode_ci,MySQL 8.0以上建议 utf8mb4_0900_ai_ci

4.2 用户表结构

在工具里面执行下面的sql。

USE vue3_springboot_demo;CREATE TABLE `user` (  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户ID',  `username` varchar(50) NOT NULL COMMENT '用户名',  `password` varchar(255) NOT NULL COMMENT '密码(加密存储)',  `email` varchar(100) DEFAULT NULL COMMENT '邮箱',  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',  `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL',  `status` tinyint DEFAULT '1' COMMENT '状态:0-禁用,1-正常',  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',  PRIMARY KEY (`id`),  UNIQUE KEY `uk_username` (`username`),  UNIQUE KEY `uk_email` (`email`),  UNIQUE KEY `uk_phone` (`phone`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';

执行后,刷新或者重新打开这个数据库,就能看到新建的数据表了。

五、后端项目搭建

创建项目可以先不用管具体代码的意思,大概看一下结构,或者方法就行。

5.1 创建 SpringBoot 项目

访问 Spring Initializr 快速创建项目:

https://start.spring.io/

添加依赖:Spring Web、Spring Security、MySQL Driver、Lombok。

然后点击下面的生成,就会把这个项目下载下来了。

5.2 用工具打开项目

下载解压后,用工具打开项目

打开项目后,再打开设置,设置一下你maven的路径。

5.3 配置 pom.xml

在已生成的基础上,添加以下依赖:

<dependencies>    <!-- ...其它配置 -->    <!-- validation -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-validation</artifactId>    </dependency>    <!-- MyBatis-Plus -->    <dependency>        <groupId>com.baomidou</groupId>        <artifactId>mybatis-plus-spring-boot4-starter</artifactId>        <version>3.5.15</version>    </dependency>    <!-- JWT -->    <dependency>        <groupId>io.jsonwebtoken</groupId>        <artifactId>jjwt-api</artifactId>        <version>0.11.5</version>    </dependency>    <dependency>        <groupId>io.jsonwebtoken</groupId>        <artifactId>jjwt-impl</artifactId>        <version>0.11.5</version>        <scope>runtime</scope>    </dependency>    <dependency>        <groupId>io.jsonwebtoken</groupId>        <artifactId>jjwt-jackson</artifactId>        <version>0.11.5</version>        <scope>runtime</scope>    </dependency></dependencies>

5.4 配置 application.yml

server:  port: 8080spring:  datasource:    url: jdbc:mysql://localhost:3306/vue3_springboot_demo?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf-8    username: root    password: 你的数据库密码    driver-class-name: com.mysql.cj.jdbc.Driver  jackson:    date-format: yyyy-MM-dd HH:mm:ss    time-zone: GMT+8# MyBatis-Plus 配置mybatis-plus:  mapper-locations: classpath*:/mapper/**/*.xml  type-aliases-package: com.example.entity  configuration:    map-underscore-to-camel-case: true    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  global-config:    db-config:      id-type: auto      logic-delete-field: deleted      logic-delete-value: 1      logic-not-delete-value: 0# JWT 配置jwt:  secret: your-256-bit-secret-key-here-please-change-it-in-production  expiration: 86400000  # 24小时,单位毫秒  header: Authorization  token-prefix: "Bearer "

先来启动一下试试能不能跑的通,为了方便后面的调试和重启,这里先配一下

然后右键点击这个服务,就可以启动或者debug了,这里我们先选 Run。

如果看到右边的日志是这样的,就证明启动成功了,可以明显的看到我们的端口号。

如果发现报错,建议把报错信息丢给AI,当然也可以在评论区留下你的脚印。

5.5 项目结构设计

在项目的的 src/main/java/com/example/demo 包下,创建这些目录,也叫包。

src/main/java/com/example/demo├── config/           # 配置类├── controller/       # 控制器├── entity/          # 实体类├── mapper/          # Mapper 接口├── service/         # 服务层│   └── impl/        # 服务实现├── utils/           # 工具类├── security/        # 安全相关├── dto/             # 数据传输对象└── exception/       # 异常处理

5.6 核心代码实现

创建的时候选择默认的 class 类。

实体类 User.java

package com.example.demo.entity;import com.baomidou.mybatisplus.annotation.*;import lombok.Data;import java.time.LocalDateTime;@Data@TableName("user")public class User {    @TableId(type = IdType.AUTO)    private Long id;    private String username;    private String password;    private String email;    private String phone;    private String avatar;    private Integer status;    @TableField(fill = FieldFill.INSERT)    private LocalDateTime createTime;    @TableField(fill = FieldFill.INSERT_UPDATE)    private LocalDateTime updateTime;}

Mapper 接口 UserMapper.java

接口类要选择 interface

package com.example.demo.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.demo.entity.User;import org.apache.ibatis.annotations.Mapper;@Mapperpublic interface UserMapper extends BaseMapper<User> {}

注册请求 DTO RegisterRequest.java

package com.example.demo.dto;import jakarta.validation.constraints.*;import lombok.Data;@Datapublic class RegisterRequest {    @NotBlank(message = "用户名不能为空")    @Size(min = 3, max = 20, message = "用户名长度必须在3-20个字符之间")    @Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "用户名只能包含字母、数字和下划线")    private String username;    @NotBlank(message = "密码不能为空")    @Size(min = 6, max = 20, message = "密码长度必须在6-20个字符之间")    @Pattern(regexp = "^(?=.*[A-Za-z])(?=.*\\d)[A-Za-z\\d@$!%*#?&]+$",              message = "密码必须包含字母和数字")    private String password;    @NotBlank(message = "确认密码不能为空")    private String confirmPassword;    @Email(message = "邮箱格式不正确")    private String email;    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")    private String phone;}

登录请求 DTO LoginRequest.java

package com.example.demo.dto;import jakarta.validation.constraints.NotBlank;import lombok.Data;@Datapublic class LoginRequest {    @NotBlank(message = "用户名不能为空")    private String username;    @NotBlank(message = "密码不能为空")    private String password;}

用户名校验请求 DTO UsernameCheckRequest.java

package com.example.demo.dto;import jakarta.validation.constraints.NotBlank;import lombok.Data;@Datapublic class UsernameCheckRequest {    @NotBlank(message = "用户名不能为空")    private String username;}

登录/注册响应 DTO AuthResponse.java

package com.example.demo.dto;import lombok.AllArgsConstructor;import lombok.Builder;import lombok.Data;import lombok.NoArgsConstructor;@Data@Builder@NoArgsConstructor@AllArgsConstructorpublic class AuthResponse {    private String token;    private String tokenType;    private String username;    private String email;    private String avatar;}

统一响应结果 Result.java

package com.example.demo.dto;import lombok.Data;@Datapublic class Result<T> {    private Integer code;    private String message;    private T data;    private Result(Integer code, String message, T data) {        this.code = code;        this.message = message;        this.data = data;    }    public static <T> Result<T> success(String message, T data) {        return new Result<>(200, message, data);    }    public static <T> Result<T> success(T data) {        return new Result<>(200, "操作成功", data);    }    public static <T> Result<T> success(String message) {        return new Result<>(200, message, null);    }    public static <T> Result<T> error(String message) {        return new Result<>(400, message, null);    }    public static <T> Result<T> error(Integer code, String message) {        return new Result<>(code, message, null);    }}

JWT 工具类 JwtUtils.java

package com.example.demo.utils;import io.jsonwebtoken.*;import io.jsonwebtoken.security.Keys;import org.springframework.beans.factory.annotation.Value;import org.springframework.stereotype.Component;import javax.crypto.SecretKey;import java.nio.charset.StandardCharsets;import java.util.Date;@Componentpublic class JwtUtils {    @Value("${jwt.secret}")    private String secret;    @Value("${jwt.expiration}")    private Long expiration;    @Value("${jwt.token-prefix}")    private String tokenPrefix;    private SecretKey getSigningKey() {        return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));    }    // 生成 Token    public String generateToken(String username) {        Date now = new Date();        Date expiryDate = new Date(now.getTime() + expiration);        return Jwts.builder()                .setSubject(username)                .setIssuedAt(now)                .setExpiration(expiryDate)                .signWith(getSigningKey())                .compact();    }    // 从 Token 中获取用户名    public String getUsernameFromToken(String token) {        Claims claims = Jwts.parserBuilder()                .setSigningKey(getSigningKey())                .build()                .parseClaimsJws(token)                .getBody();        return claims.getSubject();    }    // 验证 Token    public boolean validateToken(String token) {        try {            Jwts.parserBuilder()                .setSigningKey(getSigningKey())                .build()                .parseClaimsJws(token);            return true;        } catch (JwtException | IllegalArgumentException e) {            return false;        }    }    public String getTokenPrefix() {        return tokenPrefix;    }}

服务接口 UserService.java

package com.example.demo.service;import com.baomidou.mybatisplus.extension.service.IService;import com.example.demo.dto.AuthResponse;import com.example.demo.dto.LoginRequest;import com.example.demo.dto.RegisterRequest;import com.example.demo.entity.User;/** * 用户服务接口 *  * 继承 MyBatis-Plus 的 IService,提供基础 CRUD 功能 * 并扩展用户登录、注册等业务方法 */public interface UserService extends IService<User> {    /**     * 用户登录     *     * @param loginRequest 登录请求参数(用户名、密码)     * @return 登录结果(包含 token、用户信息等)     */    AuthResponse login(LoginRequest loginRequest);    /**     * 用户注册     *     * @param registerRequest 注册请求参数     * @return 注册结果(包含 token、用户信息等)     */    AuthResponse register(RegisterRequest registerRequest);    /**     * 根据用户名查询用户     *     * @param username 用户名     * @return 用户信息     */    User findByUsername(String username);    /**     * 检查用户名是否存在     *     * @param username 用户名     * @return true 存在 / false 不存在     */    boolean checkUsernameExists(String username);    /**     * 检查邮箱是否存在     *     * @param email 邮箱     * @return true 存在 / false 不存在     */    boolean checkEmailExists(String email);    /**     * 检查手机号是否存在     *     * @param phone 手机号     * @return true 存在 / false 不存在     */    boolean checkPhoneExists(String phone);}

这里注意一下项目结构,因为刚刚在 service 包下创建了一个 impl 的包,直接创建会把 UserService 接口类放到 impl这个包下了。

可以右键重命名,把 .impl 删掉,等创建了这个接口类后再创建。

服务实现 UserServiceImpl.java

package com.example.demo.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.demo.dto.AuthResponse;import com.example.demo.dto.LoginRequest;import com.example.demo.dto.RegisterRequest;import com.example.demo.entity.User;import com.example.demo.mapper.UserMapper;import com.example.demo.service.UserService;import com.example.demo.utils.JwtUtils;import lombok.RequiredArgsConstructor;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Transactional;@Service@RequiredArgsConstructorpublic class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {    private final JwtUtils jwtUtils;    private final BCryptPasswordEncoder passwordEncoder;    @Override    public AuthResponse login(LoginRequest loginRequest) {        // 查询用户        User user = findByUsername(loginRequest.getUsername());        if (user == null) {            throw new RuntimeException("用户名或密码错误");        }        // 验证密码        if (!passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {            throw new RuntimeException("用户名或密码错误");        }        // 检查用户状态        if (user.getStatus() == 0) {            throw new RuntimeException("账号已被禁用");        }        // 生成 Token        String token = jwtUtils.generateToken(user.getUsername());        return AuthResponse.builder()                .token(token)                .tokenType(jwtUtils.getTokenPrefix().trim())                .username(user.getUsername())                .email(user.getEmail())                .avatar(user.getAvatar())                .build();    }    @Override    @Transactional    public AuthResponse register(RegisterRequest registerRequest) {        // 校验两次密码是否一致        if (!registerRequest.getPassword().equals(registerRequest.getConfirmPassword())) {            throw new RuntimeException("两次输入的密码不一致");        }        // 检查用户名是否已存在        if (checkUsernameExists(registerRequest.getUsername())) {            throw new RuntimeException("用户名已被注册");        }        // 检查邮箱是否已存在        if (registerRequest.getEmail() != null && checkEmailExists(registerRequest.getEmail())) {            throw new RuntimeException("邮箱已被注册");        }        // 检查手机号是否已存在        if (registerRequest.getPhone() != null && checkPhoneExists(registerRequest.getPhone())) {            throw new RuntimeException("手机号已被注册");        }        // 创建用户        User user = new User();        user.setUsername(registerRequest.getUsername());        user.setPassword(passwordEncoder.encode(registerRequest.getPassword()));        user.setEmail(registerRequest.getEmail());        user.setPhone(registerRequest.getPhone());        user.setStatus(1);        save(user);        // 注册成功后自动生成 Token(可选,也可以让用户重新登录)        String token = jwtUtils.generateToken(user.getUsername());        return AuthResponse.builder()                .token(token)                .tokenType(jwtUtils.getTokenPrefix().trim())                .username(user.getUsername())                .email(user.getEmail())                .avatar(user.getAvatar())                .build();    }    @Override    public User findByUsername(String username) {        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();        wrapper.eq(User::getUsername, username);        return getOne(wrapper);    }    @Override    public boolean checkUsernameExists(String username) {        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();        wrapper.eq(User::getUsername, username);        return count(wrapper) > 0;    }    @Override    public boolean checkEmailExists(String email) {        if (email == null) return false;        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();        wrapper.eq(User::getEmail, email);        return count(wrapper) > 0;    }    @Override    public boolean checkPhoneExists(String phone) {        if (phone == null) return false;        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();        wrapper.eq(User::getPhone, phone);        return count(wrapper) > 0;    }}

全局异常处理器 GlobalExceptionHandler.java

package com.example.demo.exception;import com.example.demo.dto.Result;import org.springframework.http.HttpStatus;import org.springframework.validation.BindException;import org.springframework.validation.FieldError;import org.springframework.web.bind.MethodArgumentNotValidException;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.ResponseStatus;import org.springframework.web.bind.annotation.RestControllerAdvice;import java.util.stream.Collectors;@RestControllerAdvicepublic class GlobalExceptionHandler {    @ExceptionHandler(RuntimeException.class)    @ResponseStatus(HttpStatus.BAD_REQUEST)    public Result<String> handleRuntimeException(RuntimeException e) {        return Result.error(e.getMessage());    }    @ExceptionHandler(MethodArgumentNotValidException.class)    @ResponseStatus(HttpStatus.BAD_REQUEST)    public Result<String> handleValidationException(MethodArgumentNotValidException e) {        String message = e.getBindingResult().getFieldErrors()                .stream()                .map(FieldError::getDefaultMessage)                .collect(Collectors.joining(", "));        return Result.error(message);    }    @ExceptionHandler(BindException.class)    @ResponseStatus(HttpStatus.BAD_REQUEST)    public Result<String> handleBindException(BindException e) {        String message = e.getBindingResult().getFieldErrors()                .stream()                .map(FieldError::getDefaultMessage)                .collect(Collectors.joining(", "));        return Result.error(message);    }    @ExceptionHandler(Exception.class)    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)    public Result<String> handleException(Exception e) {        e.printStackTrace();        return Result.error(500, "服务器内部错误");    }}

控制器 AuthController.java

package com.example.demo.controller;import com.example.demo.dto.*;import com.example.demo.service.UserService;import jakarta.validation.Valid;import lombok.RequiredArgsConstructor;import org.springframework.web.bind.annotation.*;@RestController@RequestMapping("/api/auth")@RequiredArgsConstructorpublic class AuthController {    private final UserService userService;    @PostMapping("/login")    public Result<AuthResponse> login(@Valid @RequestBody LoginRequest loginRequest) {        AuthResponse response = userService.login(loginRequest);        return Result.success("登录成功", response);    }    @PostMapping("/register")    public Result<AuthResponse> register(@Valid @RequestBody RegisterRequest registerRequest) {        AuthResponse response = userService.register(registerRequest);        return Result.success("注册成功", response);    }    @PostMapping("/check-username")    public Result<Boolean> checkUsername(@Valid @RequestBody UsernameCheckRequest request) {        boolean exists = userService.checkUsernameExists(request.getUsername());        return Result.success(exists ? "用户名已被占用" : "用户名可用", !exists);    }    @GetMapping("/test")    public Result<String> test() {        return Result.success("接口连通成功");    }}

安全配置 SecurityConfig.java

package com.example.demo.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;import org.springframework.security.config.http.SessionCreationPolicy;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.web.SecurityFilterChain;@Configuration@EnableWebSecuritypublic class SecurityConfig {    @Bean    public BCryptPasswordEncoder passwordEncoder() {        return new BCryptPasswordEncoder();    }    @Bean    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {        http            .csrf(csrf -> csrf.disable())            .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))            .authorizeHttpRequests(auth -> auth                .requestMatchers(                    "/api/auth/login",                    "/api/auth/register",                    "/api/auth/check-username",                    "/api/auth/test"                ).permitAll()                .anyRequest().authenticated()            );        return http.build();    }}

跨域配置 SecurityConfig.java

package com.example.demo.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.cors.CorsConfiguration;import org.springframework.web.cors.UrlBasedCorsConfigurationSource;import org.springframework.web.filter.CorsFilter;import java.util.Arrays;@Configurationpublic class CorsConfig {    @Bean    public CorsFilter corsFilter() {        CorsConfiguration config = new CorsConfiguration();        // 允许的域名(前端地址)        config.setAllowedOrigins(Arrays.asList(                "http://localhost:5173",                "http://localhost:5174",                "http://127.0.0.1:5173"        ));        // 允许的请求方法        config.setAllowedMethods(Arrays.asList(                "GET",                "POST",                "PUT",                "DELETE",                "OPTIONS",                "PATCH"        ));        // 允许的请求头        config.setAllowedHeaders(Arrays.asList(                "Authorization",                "Content-Type",                "X-Requested-With",                "Accept",                "Origin",                "Access-Control-Request-Method",                "Access-Control-Request-Headers"        ));        // 允许暴露的响应头(前端可以访问的头信息)        config.setExposedHeaders(Arrays.asList(                "Authorization",                "Content-Disposition"        ));        // 是否允许携带 Cookie        config.setAllowCredentials(true);        // 预检请求的缓存时间(秒)        config.setMaxAge(3600L);        // 应用 CORS 配置到所有路径        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();        source.registerCorsConfiguration("/**", config);        return new CorsFilter(source);    }}

好了,springboot后端的代码暂时完成了,来看下整体的代码文件结构。

可真折腾,来启动一下项目,然后用 postman 测下 http://127.0.0.1:8080/api/auth/test 这个接口。

可算是跑通了。

如果有报错,建议报错信息丢给ai或者在评论区留言。

六、前端项目搭建

6.1 创建 Vue3 项目

cmd 打开命令执行窗口,执行 Vite 创建项目这个命令。

# 使用 Vite 创建项目npm create vite@latest vue-login-demo -- --template vue

然后打开 http://localhost:5173/,得到界面如下。

我们现在关掉命令执行窗口,用 vscode 打开项目,在项目根目录右键打开。

然后执行下面的命令

# 安装额外依赖npm install axios element-plus vue-router@4

执行后可以从 package.json 看到是否已经安装。

6.2 项目结构设计

src/├── api/                    # API 接口│   └── auth.ts├── assets/                 # 静态资源├── components/             # 公共组件├── router/                 # 路由配置│   └── index.ts├── utils/                  # 工具函数│   └── request.ts├── types/                  # 类型定义│   └── auth.ts├── views/                  # 页面组件│   ├── Login.vue│   ├── Register.vue│   └── Home.vue├── App.vue                 # 根组件├── main.ts                 # 入口文件└── vite-env.d.ts          # Vite 环境类型声明

6.3 类型定义

创建 src/types/auth.ts

// 登录请求参数export interface LoginRequest {  username: string  password: string}// 注册请求参数export interface RegisterRequest {  username: string  password: string  confirmPassword: string  email?: string  phone?: string}// 用户名校验请求export interface UsernameCheckRequest {  username: string}// 认证响应数据export interface AuthResponse {  token: string  tokenType: string  username: string  email: string | null  avatar: string | null}// API 统一响应结构export interface ApiResponse<T = any> {  code: number  message: string  data: T}// 用户信息(存储用)export interface UserInfo {  username: string  email: string | null  avatar: string | null}// 用户名校验状态export interface UsernameCheckStatus {  type: 'success' | 'danger' | 'info' | 'warning'  text: string}// 表单规则类型export interface FormRule {  required?: boolean  message?: string  trigger?: string | string[]  min?: number  max?: number  pattern?: RegExp  validator?: (rule: any, value: any, callback: any) => void}

6.4 配置 main.ts

import { createApp } from 'vue'import ElementPlus from 'element-plus'import 'element-plus/dist/index.css'import * as ElementPlusIconsVue from '@element-plus/icons-vue'import App from './App.vue'import router from './router'const app = createApp(App)// 注册所有图标for (const [key, component] of Object.entries(ElementPlusIconsVue)) {  app.component(key, component)}app.use(ElementPlus)app.use(router)app.mount('#app')

6.5 封装 Axios 请求

创建 src/utils/request.ts

import axios from 'axios'import type { AxiosError, InternalAxiosRequestConfig, AxiosResponse } from 'axios'import { ElMessage } from 'element-plus'import type { ApiResponse } from '@/types/auth'// 创建 axios 实例const request = axios.create({  baseURL: 'http://localhost:8080/api',  timeout: 10000})// 请求拦截器request.interceptors.request.use(  (config: InternalAxiosRequestConfig) => {    // 从 localStorage 获取 token 并添加到请求头    const token = localStorage.getItem('token')    if (token && config.headers) {      config.headers['Authorization'] = `Bearer ${token}`    }    return config  },  (error: AxiosError) => {    return Promise.reject(error)  })// 响应拦截器request.interceptors.response.use(  (response: AxiosResponse<ApiResponse>) => {    const res = response.data    // 根据后端返回的状态码处理    if (res.code !== 200) {      ElMessage.error(res.message || '请求失败')      return Promise.reject(new Error(res.message || '请求失败'))    }    return response  },  (error: AxiosError<ApiResponse>) => {    const message = error.response?.data?.message || error.message || '网络错误'    ElMessage.error(message)    return Promise.reject(error)  })export default request

6.6 编写 API 接口

创建 src/api/auth.ts

import request from '@/utils/request'import type {  LoginRequest,  RegisterRequest,  UsernameCheckRequest,  AuthResponse,  ApiResponse} from '@/types/auth'// 登录接口export function login(data: LoginRequest): Promise<ApiResponse<AuthResponse>> {  return request({    url: '/auth/login',    method: 'post',    data  }).then(res => res.data)}// 注册接口export function register(data: RegisterRequest): Promise<ApiResponse<AuthResponse>> {  return request({    url: '/auth/register',    method: 'post',    data  }).then(res => res.data)}// 检查用户名是否存在export function checkUsername(username: string): Promise<ApiResponse<boolean>> {  return request({    url: '/auth/check-username',    method: 'post',    data: { username } as UsernameCheckRequest  }).then(res => res.data)}// 测试接口export function testApi(): Promise<ApiResponse<string>> {  return request({    url: '/auth/test',    method: 'get'  }).then(res => res.data)}

6.7 创建登录页面

创建 src/views/Login.vue

<template>  <div class="login-container">    <el-card class="login-card">      <template #header>        <div class="card-header">          <h2>用户登录</h2>        </div>      </template>      <el-form        ref="loginFormRef"        :model="loginForm"        :rules="rules"        label-width="80px"      >        <el-form-item label="用户名" prop="username">          <el-input            v-model="loginForm.username"            placeholder="请输入用户名"            :prefix-icon="User"            clearable          />        </el-form-item>        <el-form-item label="密码" prop="password">          <el-input            v-model="loginForm.password"            type="password"            placeholder="请输入密码"            :prefix-icon="Lock"            show-password            clearable          />        </el-form-item>        <el-form-item>          <el-button            type="primary"            :loading="loading"            @click="handleLogin"            style="width: 100%"          >            登录          </el-button>        </el-form-item>        <div class="form-footer">          <span>还没有账号?</span>          <el-link type="primary" @click="goToRegister">立即注册</el-link>        </div>      </el-form>    </el-card>  </div></template><script setup lang="ts">import { ref, reactive } from 'vue'import { useRouter } from 'vue-router'import { ElMessage, type FormInstance, type FormRules } from 'element-plus'import { User, Lock } from '@element-plus/icons-vue'import { login } from '@/api/auth'import type { LoginRequest } from '@/types/auth'const router = useRouter()const loginFormRef = ref<FormInstance>()const loading = ref(false)const loginForm = reactive<LoginRequest>({  username: '',  password: ''})// 表单验证规则const rules: FormRules = {  username: [    { required: true, message: '请输入用户名', trigger: 'blur' },    { min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' }  ],  password: [    { required: true, message: '请输入密码', trigger: 'blur' },    { min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' }  ]}// 处理登录const handleLogin = async () => {  if (!loginFormRef.value) return  await loginFormRef.value.validate(async (valid) => {    if (!valid) return    loading.value = true    try {      const res = await login(loginForm)      if (res.code === 200) {        // 保存 token 和用户信息        localStorage.setItem('token', res.data.token)        localStorage.setItem('userInfo', JSON.stringify(res.data))        ElMessage.success('登录成功!')        // 跳转到首页        setTimeout(() => {          router.push('/home')        }, 1000)      }    } catch (error) {      console.error('登录失败:', error)    } finally {      loading.value = false    }  })}// 跳转到注册页const goToRegister = () => {  router.push('/register')}</script><style scoped>.login-container {  display: flex;  justify-content: center;  align-items: center;  min-height: 100vh;  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);}.login-card {  width: 420px;  border-radius: 8px;}.card-header {  text-align: center;}.card-header h2 {  margin: 0;  color: #333;}.form-footer {  display: flex;  justify-content: center;  align-items: center;  gap: 8px;  margin-top: 10px;}.tips {  text-align: center;  margin-top: 10px;}</style>

6.8 创建注册页面

创建 src/views/Register.vue

<template>  <div class="register-container">    <el-card class="register-card">      <template #header>        <div class="card-header">          <h2>用户注册</h2>        </div>      </template>      <el-form        ref="registerFormRef"        :model="registerForm"        :rules="rules"        label-width="100px"      >        <el-form-item label="用户名" prop="username">          <el-input            v-model="registerForm.username"            placeholder="请输入用户名(3-20位字母/数字/下划线)"            :prefix-icon="User"            clearable            @blur="checkUsernameExists"          >            <template #append v-if="usernameCheckStatus">              <el-tag :type="usernameCheckStatus.type" size="small">                {{ usernameCheckStatus.text }}              </el-tag>            </template>          </el-input>        </el-form-item>        <el-form-item label="密码" prop="password">          <el-input            v-model="registerForm.password"            type="password"            placeholder="请输入密码(6-20位,必须包含字母和数字)"            :prefix-icon="Lock"            show-password            clearable          />          <div class="password-strength" v-if="registerForm.password">            <el-progress              :percentage="passwordStrength"              :color="passwordStrengthColor"              :stroke-width="6"            />          </div>        </el-form-item>        <el-form-item label="确认密码" prop="confirmPassword">          <el-input            v-model="registerForm.confirmPassword"            type="password"            placeholder="请再次输入密码"            :prefix-icon="Lock"            show-password            clearable          />        </el-form-item>        <el-form-item label="邮箱" prop="email">          <el-input            v-model="registerForm.email"            placeholder="请输入邮箱"            :prefix-icon="Message"            clearable          />        </el-form-item>        <el-form-item label="手机号" prop="phone">          <el-input            v-model="registerForm.phone"            placeholder="请输入手机号"            :prefix-icon="Phone"            clearable          />        </el-form-item>        <el-form-item>          <el-button            type="primary"            :loading="loading"            @click="handleRegister"            style="width: 100%"          >            注册          </el-button>        </el-form-item>        <div class="form-footer">          <span>已有账号?</span>          <el-link type="primary" @click="goToLogin">立即登录</el-link>        </div>      </el-form>    </el-card>  </div></template><script setup lang="ts">import { ref, reactive, computed, watch } from 'vue'import { useRouter } from 'vue-router'import { ElMessage, type FormInstance, type FormRules } from 'element-plus'import { User, Lock, Message, Phone } from '@element-plus/icons-vue'import { register, checkUsername } from '@/api/auth'import type { RegisterRequest, UsernameCheckStatus } from '@/types/auth'const router = useRouter()const registerFormRef = ref<FormInstance>()const loading = ref(false)const registerForm = reactive<RegisterRequest>({  username: '',  password: '',  confirmPassword: '',  email: '',  phone: ''})// 用户名校验状态const usernameCheckStatus = ref<UsernameCheckStatus | null>(null)// 验证确认密码const validateConfirmPassword = (_rule: any, value: string, callback: (error?: Error) => void) => {  if (value === '') {    callback(new Error('请再次输入密码'))  } else if (value !== registerForm.password) {    callback(new Error('两次输入的密码不一致'))  } else {    callback()  }}// 表单验证规则const rules: FormRules = {  username: [    { required: true, message: '请输入用户名', trigger: 'blur' },    { min: 3, max: 20, message: '用户名长度在 3 到 20 个字符', trigger: 'blur' },    { pattern: /^[a-zA-Z0-9_]+$/, message: '用户名只能包含字母、数字和下划线', trigger: 'blur' }  ],  password: [    { required: true, message: '请输入密码', trigger: 'blur' },    { min: 6, max: 20, message: '密码长度在 6 到 20 个字符', trigger: 'blur' },    {      pattern: /^(?=.*[A-Za-z])(?=.*\d)/,      message: '密码必须包含字母和数字',      trigger: 'blur'    }  ],  confirmPassword: [    { required: true, validator: validateConfirmPassword, trigger: 'blur' }  ],  email: [    { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }  ],  phone: [    { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }  ]}// 密码强度计算const passwordStrength = computed(() => {  const pwd = registerForm.password  if (!pwd) return 0  let strength = 0  if (pwd.length >= 6) strength += 20  if (pwd.length >= 10) strength += 20  if (/[a-z]/.test(pwd) && /[A-Z]/.test(pwd)) strength += 20  if (/\d/.test(pwd)) strength += 20  if (/[^a-zA-Z0-9]/.test(pwd)) strength += 20  return Math.min(strength, 100)})// 密码强度颜色const passwordStrengthColor = computed(() => {  const strength = passwordStrength.value  if (strength < 40) return '#f56c6c'  if (strength < 70) return '#e6a23c'  return '#67c23a'})// 监听密码变化,触发确认密码校验watch(() => registerForm.password, () => {  if (registerForm.confirmPassword) {    registerFormRef.value?.validateField('confirmPassword')  }})// 检查用户名是否存在const checkUsernameExists = async () => {  if (!registerForm.username || registerForm.username.length < 3) {    usernameCheckStatus.value = null    return  }  try {    const res = await checkUsername(registerForm.username)    if (res.data) {      usernameCheckStatus.value = { type: 'success', text: '用户名可用' }    } else {      usernameCheckStatus.value = { type: 'danger', text: '用户名已被占用' }    }  } catch (error) {    usernameCheckStatus.value = null  }}// 处理注册const handleRegister = async () => {  if (!registerFormRef.value) return  await registerFormRef.value.validate(async (valid) => {    if (!valid) return    // 再次检查用户名    if (usernameCheckStatus.value?.type === 'danger') {      ElMessage.error('用户名已被占用,请更换')      return    }    loading.value = true    try {      const res = await register(registerForm)      if (res.code === 200) {        ElMessage.success('注册成功!即将跳转到登录页...')        setTimeout(() => {          router.push('/login')        }, 1500)      }    } catch (error) {      console.error('注册失败:', error)    } finally {      loading.value = false    }  })}// 跳转到登录页const goToLogin = () => {  router.push('/login')}</script><style scoped>.register-container {  display: flex;  justify-content: center;  align-items: center;  min-height: 100vh;  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);  padding: 20px;}.register-card {  width: 500px;  border-radius: 8px;}.card-header {  text-align: center;}.card-header h2 {  margin: 0;  color: #333;}.password-strength {  margin-top: 8px;}.form-footer {  display: flex;  justify-content: center;  align-items: center;  gap: 8px;  margin-top: 10px;}</style>

6.9 创建首页

创建 src/views/Home.vue

<template>  <div class="home-container">    <el-card class="welcome-card">      <template #header>        <div class="card-header">          <h1>欢迎回来,{{ userInfo.username }}!</h1>        </div>      </template>      <el-descriptions title="用户信息" :column="1" border>        <el-descriptions-item label="用户名">          {{ userInfo.username }}        </el-descriptions-item>        <el-descriptions-item label="邮箱">          {{ userInfo.email || '未设置' }}        </el-descriptions-item>        <el-descriptions-item label="Token">          <el-tag type="success">{{ tokenPreview }}</el-tag>        </el-descriptions-item>        <el-descriptions-item label="头像">          <el-avatar v-if="userInfo.avatar" :src="userInfo.avatar">            {{ userInfo.username?.charAt(0) }}          </el-avatar>          <span v-else>未设置</span>        </el-descriptions-item>      </el-descriptions>      <div class="actions">        <el-button type="primary" @click="testConnection">          测试接口连通性        </el-button>        <el-button type="danger" @click="handleLogout">          退出登录        </el-button>      </div>      <el-alert        v-if="testResult"        :title="testResult"        type="success"        :closable="false"        style="margin-top: 20px"      />    </el-card>  </div></template><script setup lang="ts">import { ref, computed } from 'vue'import { useRouter } from 'vue-router'import { ElMessage, ElMessageBox } from 'element-plus'import { testApi } from '@/api/auth'import type { UserInfo } from '@/types/auth'const router = useRouter()// 从 localStorage 获取用户信息const getUserInfoFromStorage = ():UserInfo => {  const stored = localStorage.getItem('userInfo')  if (stored) {    try {      return JSON.parse(stored)    } catch {      return { username: '', email: null, avatar: null }    }  }  return { username: '', email: null, avatar: null }}const userInfo = ref<UserInfo>(getUserInfoFromStorage())const testResult = ref('')// Token 预览const tokenPreview = computed(() => {  const token = localStorage.getItem('token') || ''  return token.length > 20 ? token.substring(0, 20) + '...' : token})// 测试接口连通性const testConnection = async () => {  try {    const res = await testApi()    testResult.value = res.message    ElMessage.success('接口连通测试成功!')  } catch (error) {    ElMessage.error('接口连通测试失败')  }}// 退出登录const handleLogout = () => {  ElMessageBox.confirm('确定要退出登录吗?', '提示', {    confirmButtonText: '确定',    cancelButtonText: '取消',    type: 'warning'  }).then(() => {    // 清除本地存储    localStorage.removeItem('token')    localStorage.removeItem('userInfo')    ElMessage.success('已退出登录')    // 跳转到登录页    router.push('/login')  }).catch(() => {})}</script><style scoped>.home-container {  display: flex;  justify-content: center;  align-items: center;  min-height: 100vh;  background: #f5f5f5;  padding: 20px;}.welcome-card {  width: 600px;}.card-header {  text-align: center;}.card-header h1 {  margin: 0;  font-size: 24px;  color: #333;}.actions {  margin-top: 20px;  display: flex;  gap: 10px;  justify-content: center;}</style>

6.10 配置路由

创建 src/router/index.ts

import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router'import { ElMessage } from 'element-plus'const routes: RouteRecordRaw[] = [  {    path: '/',    redirect: '/login'  },  {    path: '/login',    name: 'Login',    component: () => import('@/views/Login.vue')  },  {    path: '/register',    name: 'Register',    component: () => import('@/views/Register.vue')  },  {    path: '/home',    name: 'Home',    component: () => import('@/views/Home.vue'),    meta: { requiresAuth: true }  }]const router = createRouter({  history: createWebHistory(),  routes})// 路由守卫router.beforeEach((to, _from, next) => {  const token = localStorage.getItem('token')  if (to.meta.requiresAuth && !token) {    ElMessage.warning('请先登录')    next('/login')  } else if ((to.path === '/login' || to.path === '/register') && token) {    // 已登录用户访问登录/注册页,重定向到首页    next('/home')  } else {    next()  }})export default router

6.11 修改 App.vue

<template>  <router-view /></template><script setup lang="ts"></script><style>* {  margin: 0;  padding: 0;  box-sizing: border-box;}body {  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;}</style>

6.12 添加 Vite 环境类型声明(可选)

如果 vite-env.d.ts 不存在,创建它:

/// <reference types="vite/client" />declare module '*.vue' {  import type { DefineComponent } from 'vue'  const component: DefineComponent<{}, {}, any>  export default component}

6.13 配置 tsconfig.json(调整)

确保 tsconfig.json 包含以下配置:

{  "files": [],  "references": [    { "path": "./tsconfig.app.json" },    { "path": "./tsconfig.node.json" }  ]}

tsconfig.app.json 包含以下配置:

{  "extends": "@vue/tsconfig/tsconfig.dom.json",  "compilerOptions": {    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",    "types": ["vite/client"],    "baseUrl": ".",    "paths": {      "@/*": ["./src/*"]    },    "ignoreDeprecations": "6.0",    "verbatimModuleSyntax":false,  // 关闭严格模式    /* Linting */    "noUnusedLocals":true,    "noUnusedParameters":true,    "erasableSyntaxOnly":true,    "noFallthroughCasesInSwitch":true  },  "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]}

tsconfig.node.json 包含以下配置:

{  "compilerOptions": {    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",    "target": "ES2023",    "lib": ["ES2023"],    "module": "ESNext",    "types": ["node"],    "skipLibCheck":true,    /* Bundler mode */    "moduleResolution": "bundler",    "allowImportingTsExtensions":true,    "verbatimModuleSyntax":true,    "moduleDetection": "force",    "noEmit":true,    /* Linting */    "noUnusedLocals":true,    "noUnusedParameters":true,    "erasableSyntaxOnly":true,    "noFallthroughCasesInSwitch":true  },  "include": ["vite.config.ts"]}

6.14 配置 vite.config.ts

import { defineConfig } from 'vite'import vue from '@vitejs/plugin-vue'import { resolve } from 'path'// https://vitejs.dev/config/export default defineConfig({  plugins: [vue()],  resolve: {    alias: {      '@': resolve(__dirname, './src')    }  },  server: {    port: 5173,    host: true  }})

以上就是完整的前端 TypeScript 版本实现。

现在我们来启动一下

npm run dev

可以看到界面啦。

以上就是完整注册登录的完整代码了,能看到这里的你,看来你的执行力也不简单,非常感谢你的观看。

如果这篇文章对你有帮助,欢迎点赞、在看支持一下。

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-04-15 02:40:19 HTTP/1.1 GET : https://www.yeyulingfeng.com/a/529075.html
  2. 运行时间 : 0.103459s [ 吞吐率:9.67req/s ] 内存消耗:5,213.02kb 文件加载:145
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=2d5948af0445d0443bf2a0673f76203d
  1. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/composer/autoload_static.php ( 6.05 KB )
  7. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/ralouphie/getallheaders/src/getallheaders.php ( 1.60 KB )
  10. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  11. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  12. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  13. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  14. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  15. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  16. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  17. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  18. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  19. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions_include.php ( 0.16 KB )
  21. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/guzzlehttp/guzzle/src/functions.php ( 5.54 KB )
  22. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  23. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  24. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  25. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/provider.php ( 0.19 KB )
  26. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  27. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  28. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  29. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/common.php ( 0.03 KB )
  30. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  32. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/alipay.php ( 3.59 KB )
  33. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  34. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/app.php ( 0.95 KB )
  35. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cache.php ( 0.78 KB )
  36. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/console.php ( 0.23 KB )
  37. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/cookie.php ( 0.56 KB )
  38. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/database.php ( 2.48 KB )
  39. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/filesystem.php ( 0.61 KB )
  40. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/lang.php ( 0.91 KB )
  41. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/log.php ( 1.35 KB )
  42. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/middleware.php ( 0.19 KB )
  43. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/route.php ( 1.89 KB )
  44. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/session.php ( 0.57 KB )
  45. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/trace.php ( 0.34 KB )
  46. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/config/view.php ( 0.82 KB )
  47. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/event.php ( 0.25 KB )
  48. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  49. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/service.php ( 0.13 KB )
  50. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/AppService.php ( 0.26 KB )
  51. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  52. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  53. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  54. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  55. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  56. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/services.php ( 0.14 KB )
  57. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  58. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  59. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  60. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  61. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  62. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  63. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  64. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  65. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  66. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  67. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  68. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  69. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  70. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  71. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  72. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  73. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  74. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  75. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  76. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  77. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  78. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  79. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  80. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  81. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  82. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  83. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  84. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  85. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  86. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  87. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/Request.php ( 0.09 KB )
  88. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  89. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/middleware.php ( 0.25 KB )
  90. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  91. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  92. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  93. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  94. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  95. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  96. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  97. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  98. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  99. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  100. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  101. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  102. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  103. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/route/app.php ( 3.94 KB )
  104. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  105. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  106. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Index.php ( 9.87 KB )
  108. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/BaseController.php ( 2.05 KB )
  109. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  110. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  111. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  112. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  113. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  114. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  115. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  116. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  117. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  118. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  119. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  120. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  121. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  122. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  123. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  124. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  125. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  126. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  127. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  128. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  129. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  130. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  131. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  132. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  133. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  134. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  135. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/app/controller/Es.php ( 3.30 KB )
  136. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  137. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  138. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  139. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  140. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  141. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  142. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  143. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  144. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/runtime/temp/c935550e3e8a3a4c27dd94e439343fdf.php ( 31.80 KB )
  145. /yingpanguazai/ssd/ssd1/www/wwww.yeyulingfeng.com/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000522s ] mysql:host=127.0.0.1;port=3306;dbname=wenku;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000747s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000311s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000243s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000474s ]
  6. SELECT * FROM `set` [ RunTime:0.002609s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000606s ]
  8. SELECT * FROM `article` WHERE `id` = 529075 LIMIT 1 [ RunTime:0.000816s ]
  9. UPDATE `article` SET `lasttime` = 1776192019 WHERE `id` = 529075 [ RunTime:0.016465s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 64 LIMIT 1 [ RunTime:0.000252s ]
  11. SELECT * FROM `article` WHERE `id` < 529075 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000456s ]
  12. SELECT * FROM `article` WHERE `id` > 529075 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000362s ]
  13. SELECT * FROM `article` WHERE `id` < 529075 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.000889s ]
  14. SELECT * FROM `article` WHERE `id` < 529075 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000800s ]
  15. SELECT * FROM `article` WHERE `id` < 529075 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001007s ]
0.105171s