6、登录功能后端开发
https://xiaoxueblog.com/ai/%E7%99%BB%E5%BD%95%E5%8A%9F%E8%83%BD%E5%90%8E%E7%AB%AF%E5%BC%80%E5%8F%91.html
1、新建用户表SQL脚本
-- CREATE DATABASE aicloud CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;-- 创建用户表
drop table if exists t_user;
CREATE TABLE `t_user`
(id bigint not null comment '主键 ID',`username` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '用户名',`password` VARCHAR(200) NOT NULL DEFAULT '' COMMENT '密码',`photo` VARCHAR(20) DEFAULT NULL COMMENT '头像',`phone` VARCHAR(20) DEFAULT NULL COMMENT '手机',`status` INT NOT NULL DEFAULT '1' COMMENT '状态 1、启用 2、禁用',`use_count` INT NOT NULL DEFAULT '0' COMMENT '每天可以使用大模型的次数',create_id bigint DEFAULT null comment '创建人ID',create_by varchar(30) DEFAULT null comment '创建人',create_time datetime DEFAULT null comment '创建时间',update_by varchar(30) DEFAULT null comment '修改人',update_time datetime DEFAULT null comment '修改时间',deleted smallint default 0 not null comment '删除 0、否 1、是',PRIMARY KEY (`id`)
) ENGINE = INNODBDEFAULT CHARSET = utf8mb4 COMMENT ='用户表';INSERT INTO t_user (id, username, password, photo, phone, status, use_count, create_id, create_by, create_time, update_by, update_time, deleted) VALUES (1, 'admin', '123456', null, null, 1, 0, null, null, null, null, null, 0);
--INSERT INTO t_user (id, username, password, photo, phone, status, use_count, create_id, create_by, create_time, update_by, update_time, deleted) VALUES (1, 'admin', '$2a$10$UceAABzbebzO5929OXo6auLgqaCOlfbQc6q.G4YqoVcerWPsGuIZa', null, null, 1, 0, null, null, null, null, null, 0);
2、BaseEntity
package com.xx.entities;import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;import java.util.Date;/*** @Author: xueqimiao* @Date: 2024/11/13 15:55*/
@Setter
@Getter
public class BaseEntity {/*** 创建人ID*/@TableField(fill = FieldFill.INSERT)protected Long createId;/*** 创建人*/@TableField(fill = FieldFill.INSERT)protected String createBy;/*** 创建时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@TableField(fill = FieldFill.INSERT)protected Date createTime;/*** 修改人*/@TableField(fill = FieldFill.UPDATE)protected String updateBy;/*** 修改时间*/@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")@TableField(fill = FieldFill.UPDATE)protected Date updateTime;/*** 删除 0、否 1、是*/@JsonIgnore@TableLogic(value = "0", delval = "1")private Integer deleted;
}
3、User
package com.xx.entities;import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.io.Serializable;/*** 用户表** @TableName t_user*/
@TableName(value = "t_user")
@Data
public class User extends BaseEntity implements Serializable {@TableId@Schema(description = "")private Long id;@Schema(description = "用户名")private String username;@Schema(description = "密码")private String password;@Schema(description = "头像")private String photo;@Schema(description = "手机")private String phone;@Schema(description = "状态 1、启用 2、禁用")private Integer status;@Schema(description = "每天可以使用大模型的次数")private Integer useCount;
}
package com.xx.dto;import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.io.Serializable;/*** @Author: xueqimiao* @Date: 2025/3/5 14:34*/
@Data
public class LoginDTO implements Serializable {@Schema(description = "用户名")private String username;@Schema(description = "密码")private String password;
}
package com.xx.vo;import com.baomidou.mybatisplus.annotation.TableId;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;import java.io.Serializable;/*** @Author: xueqimiao* @Date: 2025/3/5 14:38*/
@Data
public class LoginVO implements Serializable {@TableId@Schema(description = "")private Long id;@Schema(description = "用户名")private String username;@Schema(description = "密码")private String password;@Schema(description = "头像")private String photo;@Schema(description = "手机")private String phone;@Schema(description = "状态 1、启用 2、禁用")private Integer status;@Schema(description = "每天可以使用大模型的次数")private Integer useCount;
}
4、UserMapper
package com.xx.mapper;import com.xx.entities.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;/*** @author* @description 针对表【t_user(用户表)】的数据库操作Mapper* @createDate 2024-10-05 16:13:06* @Entity com.entities.User*/
public interface UserMapper extends BaseMapper<User> {}
5、UserService
package com.xx.service;import com.xx.dto.LoginDTO;
import com.xx.entities.User;
import com.baomidou.mybatisplus.extension.service.IService;
import com.xx.vo.LoginVO;/*** @author* @description 针对表【t_user(用户表)】的数据库操作Service* @createDate 2024-10-05 16:13:06*/
public interface UserService extends IService<User> {/*** 登录* @param loginDTO* @return*/LoginVO login(LoginDTO loginDTO);
}
package com.xx.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xx.dto.LoginDTO;
import com.xx.exception.BusinessException;
import com.xx.service.UserService;
import com.xx.entities.User;
import com.xx.mapper.UserMapper;
import com.xx.utils.ValidationUtil;
import com.xx.utils.ValueUtil;
import com.xx.vo.LoginVO;
import org.springframework.stereotype.Service;/*** @author* @description 针对表【t_user(用户表)】的数据库操作Service实现* @createDate 2024-10-05 16:13:06*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>implements UserService {@Overridepublic LoginVO login(LoginDTO loginDTO) {if(ValidationUtil.isEmpty(loginDTO)){throw new BusinessException("请求参数不能为空");}if(ValidationUtil.isEmpty(loginDTO.getUsername()) || ValidationUtil.isEmpty((loginDTO.getPassword()))){throw new BusinessException("用户名或者密码不能为空");}LambdaQueryWrapper<User> queryWrapper = Wrappers.lambdaQuery();queryWrapper.eq(User::getUsername,loginDTO.getUsername());User user = getOne(queryWrapper);if(ValidationUtil.isEmpty(user)){// 这里不要直接报出用户名不存在throw new BusinessException("用户名或者密码错误");}if(!user.getPassword().equals(loginDTO.getPassword())){throw new BusinessException("用户名或者密码错误");}return ValueUtil.copyFieldValue(user,LoginVO.class);}
}
6、UserController
package com.xx.controller;import com.xx.common.Result;
import com.xx.dto.LoginDTO;
import com.xx.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @auther xx* @create 2024-10-03 19:48*/
@RestController
@RequestMapping("/user")
@Tag(name = "用户管理")
public class UserController {@Resourceprivate UserService userService;@Operation(summary = "用户登录")@PostMapping(value = "/login")public Result login(@RequestBody LoginDTO loginDTO) {return Result.ok(userService.login(loginDTO));}
}
7、MybatisPlusConfig
package com.xx.config;import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Configuration;@Configuration
@MapperScan(value = {"com.xx.mapper"})
public class MybatisPlusConfig {}
8、全局异常
package com.xx.config;import com.xx.common.Result;
import com.xx.exception.BusinessException;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;/*** @Author: xueqimiao* @Date: 2025/3/5 14:48*/
@Slf4j
@RestControllerAdvice
public class GlobalExcHandler {@ExceptionHandler(BusinessException.class)@ResponseStatus(HttpStatus.OK)@ResponseBodypublic Result<String> businessException(BusinessException e, HttpServletRequest request) {log.error("系统异常", e);return Result.error(e.getMessage());}}
9、application.yml
server:port: 8001spring:application:name: xx-ai-cloudai:openai:api-key: # OpenAI API 密钥base-url: https://gitaigc.com # OpenAI 基础 URLchat:options:model: gpt-3.5-turboimage:options:model: gpt-4-dalle
# base-url: https://api.openai.com # OpenAI 基础 URL# 以下是可选的配置,已经被注释掉# spring.ai.openai.base-url: https://api.openai.com# spring.ai.openai.api-key:# 生成文本(text-to-text)模型配置# spring.ai.openai.chat.options.model: gpt-3.5-turbo# spring.ai.openai.chat.options.temperature: 0.4# 生成图像(text-to-image)模型配置
# spring.ai.openai.image.options.model: gpt-4-dalle
# spring.ai.openai.image.options.model: gpt-3.5-turbo# ========================Redis 配置=====================
---
spring:data:redis:host: 127.0.0.1 # Redis 主机password: 123456 # Redis 密码port: 6379 # Redis 端口timeout: 1s # Redis 连接超时# ========================SQL 初始化配置===================
---
spring:sql:init:mode: always # 总是初始化数据库schema-locations: classpath:db/init.sql # 初始化SQL文件位置# ========================数据库配置(Druid + MySQL 8)=======================
---
spring:datasource:type: com.alibaba.druid.pool.DruidDataSource # 数据源类型driver-class-name: com.mysql.cj.jdbc.Driver # MySQL JDBC 驱动类名url: jdbc:mysql://127.0.0.1:3306/aicloud?characterEncoding=utf8&useSSL=false&serverTimezone=GMT%2B8&rewriteBatchedStatements=true&allowPublicKeyRetrieval=true # 数据库连接 URLusername: root # 数据库用户名password: mac_root # 数据库密码druid:test-while-idle: false # 配置 Druid 数据源的空闲连接检测# ========================MyBatis-Plus 配置===================
mybatis-plus:configuration:map-underscore-to-camel-case: true # 将数据库表字段的下划线转为驼峰命名法mapper-locations: classpath:mapper/*.xml # MyBatis 映射器文件的位置type-aliases-package: com.xx.entities # MyBatis 类型别名包路径# ========================日志配置=======================
logging:level:com.xx: info # 设置特定包的日志级别# ========================JSON 日期格式配置===================
---
spring:jackson:date-format: yyyy-MM-dd HH:mm:ss # JSON 日期格式time-zone: GMT+8 # 设置时区为 GMT+8
10、登录页面
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Demo</title><!-- 请勿在项目正式环境中引用该 layui.css 地址 --><link href="/layui/css/layui.css" rel="stylesheet">
</head>
<body>
<style>.demo-login-container{width: 320px; margin: 21px auto 0;}.demo-login-other .layui-icon{position: relative; display: inline-block; margin: 0 2px; top: 2px; font-size: 26px;}
</style>
<form class="layui-form"><div class="demo-login-container"><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-username"></i></div><input type="text" name="username" value="" lay-verify="required" placeholder="用户名" lay-reqtext="请填写用户名" autocomplete="off" class="layui-input" lay-affix="clear"></div></div><div class="layui-form-item"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-password"></i></div><input type="password" name="password" value="" lay-verify="required" placeholder="密 码" lay-reqtext="请填写密码" autocomplete="off" class="layui-input" lay-affix="eye"></div></div><div class="layui-form-item"><div class="layui-row"><div class="layui-col-xs7"><div class="layui-input-wrap"><div class="layui-input-prefix"><i class="layui-icon layui-icon-vercode"></i></div><input type="text" name="captcha" value="" lay-verify="required" placeholder="验证码" lay-reqtext="请填写验证码" autocomplete="off" class="layui-input" lay-affix="clear"></div></div><div class="layui-col-xs5"><div style="margin-left: 10px;"><img src="https://www.oschina.net/action/user/captcha" onclick="this.src='https://www.oschina.net/action/user/captcha?t='+ new Date().getTime();"></div></div></div></div><div class="layui-form-item"><input type="checkbox" name="remember" lay-skin="primary" title="记住密码"><a href="#forget" style="float: right; margin-top: 7px;">忘记密码?</a></div><div class="layui-form-item"><button class="layui-btn layui-btn-fluid" lay-submit lay-filter="demo-login">登录</button></div><div class="layui-form-item demo-login-other"><label>社交账号登录</label><span style="padding: 0 21px 0 6px;"><a href="javascript:;"><i class="layui-icon layui-icon-login-qq" style="color: #3492ed;"></i></a><a href="javascript:;"><i class="layui-icon layui-icon-login-wechat" style="color: #4daf29;"></i></a><a href="javascript:;"><i class="layui-icon layui-icon-login-weibo" style="color: #cf1900;"></i></a></span>或 <a href="#reg">注册帐号</a></div></div>
</form><!-- 请勿在项目正式环境中引用该 layui.js 地址 -->
<script src="/layui/layui.js"></script>
<script>layui.use(function(){var form = layui.form;var layer = layui.layer;var jQuery = layui.$;// 提交事件form.on('submit(demo-login)', function (data) {var field = data.field; // 获取表单字段值// 此处可执行 Ajax 等操作jQuery.ajax({url: '/user/login',type: 'POST',contentType: 'application/json',data: JSON.stringify(field),success: function (result) {if (result.code == 200) {layer.msg("登录成功: "+result.result.username);} else {layer.msg("登录失败: "+result.code+"\t"+result.message);}}});return false; // 阻止默认 form 跳转});});
</script></body>
</html>