Springboot后端简易方式快速搭建
前言
快速学了下。和传统的springboot项目相比,没有用service和serviceImpl。比较不合规,但够简单。可以用于快速开发。
前后端分离。前端请另寻。
特别感谢:程序员青戈
1 开始
2 手动导入一些依赖
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--lombook 哦对 在项目创建就可以导入了-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mybatis-plus 解放增删改查-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3.1</version>
</dependency>
<!-- JWT Token相关 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
<!-- 超多好用的工具 强烈推荐 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.3</version>
</dependency>
<!-- 加密工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
3 建个数据库
4 application.properties
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/springboot-vue?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456
# 应用服务 WEB 访问端口
server.port=9090
5 返回Result
public class Result<T> {
private String code;
private String msg;
private T data;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public Result() {
}
public Result(T data) {
this.data = data;
}
public static Result success() {
Result result = new Result<>();
result.setCode("0");
result.setMsg("成功");
return result;
}
public static <T> Result<T> success(T data) {
Result<T> result = new Result<>(data);
result.setCode("0");
result.setMsg("成功");
return result;
}
public static Result error(String code, String msg) {
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
return result;
}
}
6 文件结构预览
7 entity.User
这里写实体。属性和表一一对应
注意看注释!!
@TableName("user")
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
@TableId(type = IdType.AUTO)
private Integer id;
private String username;
private String password;
private String nickName;
private Integer age;
private String sex;
private String address;
// private String avatar;
// @TableField(exist = false)
// private List<Integer> roles;
//
// @TableField(exist = false)
// private List<Book> bookList;
//
// @TableField(exist = false)
// private String token;
//
// private BigDecimal account;
//
// @TableField(exist = false)
// private Set<Permission> permissions;
}
8 mapper.UserMapper
这个BaseMapper自带增删改查。如果有其他需求可以自己写。
public interface UserMapper extends BaseMapper<User> {
}
9 controller.UserController
可以了解一下restful风格接口。
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
UserMapper userMapper;
@PostMapping
public Result<?> save(@RequestBody User user){
userMapper.insert(user);
return Result.success();
}
}
10 体验一下
在上面的controller输入:
@GetMapping("/all")
public Result<?> findAll() {
return Result.success(userMapper.selectList(null));
}
就能看到数据库的json啦 是不是很简单呢
返回的json交给前端耍了。
(一定要找一个好前端啊 就算后端写的比较拉也能高逼格)
以下是写完项目后的一些记录,包含一些重要的配置和代码。
11.一些配置
11.1 允许客户端携带验证信息 AddResponseHeaderFilter
因为本项目将使用cookie,因此必须允许客户端携带验证信息。实现的方法是继承重写spring web的OncePerRequestFilter,对每一个申请都在回复中添加请求头Access-Control-Allow-Credentials为ture。
@Component
public class AddResponseHeaderFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Credentials", "true");
filterChain.doFilter(request, response);
}
}
11.2 跨域设置 CorsConfig
前后端分离的跨域设置。允许前端的origin向后端发送请求。
@Configuration
public class CorsConfig {
// 当前跨域请求最大有效时长。这里默认1天
private static final long MAX_AGE = 24 * 60 * 60;
private CorsConfiguration buildConfig() {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedOrigin("http://127.0.0.1:前端端口"); // 1 设置访问源地址
corsConfiguration.addAllowedOrigin("https://前端.com");
corsConfiguration.addAllowedHeader("*"); // 2 设置访问源请求头
corsConfiguration.addAllowedMethod("*"); // 3 设置访问源请求方法
corsConfiguration.setMaxAge(MAX_AGE);
return corsConfiguration;
}
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", buildConfig()); // 4 对接口配置跨域设置
return new CorsFilter(source);
}
}
11.3 MybatisPlus配置 MybatisPlusConfig
该处主要配置了分页插件。后续的博客搜索、评论搜索等都会用到分页查询
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
11.4 其他配置
在application.java中的配置:引入了spring security的加密。
@SpringBootApplication(exclude= SecurityAutoConfiguration.class)
@MapperScan("com.example.QLblog.mapper")
public class SpringbootTestApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootTestApplication.class, args);
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder(){
return new BCryptPasswordEncoder();
}
}
我们只要用:
bCryptPasswordEncoder.encode(user.getPassword())
即可加密密码。当然,控制类需装配进来:
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
在application.properties中配置:(隐去了敏感信息)
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库用户名&密码:
spring.datasource.username=***
#开发版
#spring.datasource.url=jdbc:mysql://***:3306/qlblog?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
#spring.datasource.password=***
#发行版
spring.datasource.url=jdbc:mysql://localhost:3306/qlblog?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&useSSL=false&serverTimezone=GMT%2b8
spring.datasource.password=***
# 应用服务 WEB 访问端口
server.port=9090
# 文件上传ip
file.ip=***
#文件上传限额
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB
#连接sql字符集为utf8mb4 可能是多余的
spring.datasource.hikari.connection-init-sql=set names utf8mb4 collate utf8mb4_unicode_ci
spring.datasource.tomcat.init-s-q-l=set names utf8mb4 collate utf8mb4_unicode_ci
12 Cookie相关
12.1 生成Token的工具
之后生成cookie时,会先将用户信息装进token,再装进cookie。本工具类定义了通过user生成token的方法。
@Slf4j
@Component
public class TokenUtils {
@Resource
private UserMapper userMapper;
private static UserMapper staticUserMapper;
@PostConstruct
public void init() {
staticUserMapper = userMapper;
}
/**
* 生成token
* @param user
* @return
*/
public static String genToken(User user) {
return JWT.create().withExpiresAt(DateUtil.offsetDay(new Date(), 1)).withAudience(user.getId().toString())
.sign(Algorithm.HMAC256(user.getPassword()));
}
/**
* 获取token中的用户信息
* @return
*/
public static User getUser() {
try {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader("token");
String aud = JWT.decode(token).getAudience().get(0);
Integer userId = Integer.valueOf(aud);
return staticUserMapper.selectById(userId);
} catch (Exception e) {
log.error("解析token失败", e);
return null;
}
}
}
12.2 基础控制类 BaseController
因为有些操作,我们所有的controller都会使用,因此我创建了BaseController用于存放这些会反复用到的、重要的函数。如:
l 通过token获取user
l 通过cookie获取token,进而获取token
l 通过cookie判断用户登陆状态和信息
其他controller只要继承这玩意就行。
*后端的几乎所有敏感操作都会通过cookie进行身份的验证!*
@RestController
public class BaseController {
@Resource
UserMapper userMapper;
@Autowired
protected HttpServletRequest request;
/**
* 根据token获取用户信息
* @return user
*/
public User getUserFromToken() {
String token = request.getHeader("token");
String aud = JWT.decode(token).getAudience().get(0);
Integer userId = Integer.valueOf(aud);
return userMapper.selectById(userId);
}
/**
* 根据cookie获得token,获得用户信息
* @return user
*/
public User getUserFromCookie(){
String token = null;
for(int i = 0; i < request.getCookies().length; i++){
if(request.getCookies()[i].getName().equals("token")){
token = request.getCookies()[i].getValue();
}
}
if(token!=null){
String aud = JWT.decode(token).getAudience().get(0);
Integer userId = Integer.valueOf(aud);
if(userMapper.selectById(userId)!=null){
User retUser = userMapper.selectById(userId);
retUser.setPassword("");
return retUser;
}else {
return null;
}
}else {
return null;
}
}
/**
* 判断登录状态及用户是否存在
* @return boolean
*/
//验证登陆状态
public boolean verifyLoginStatus(){
if (request.getCookies() == null) {
return false;
}
String token = null;
for (int i = 0; i < request.getCookies().length; i++) {
if (request.getCookies()[i].getName().equals("token")) {
token = request.getCookies()[i].getValue();
}
}
if (token == null) return false;
return getUserFromCookie() != null;
}
/*验证登录模板:
if(!verifyLoginStatus()){
return Result.error("-1","登录状态有误,请重新登陆!");
}
*/
}
12.3 组装cookie及注销
//登录 post /login
@PostMapping("/login")
public Result<?> login(@RequestBody User userParam, HttpServletResponse response) {
User userPwd = userMapper.selectPwdByName(userParam.getUsername());
if(userPwd==null){
return Result.error("-1","用户名错误");
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", userParam.getUsername());
queryWrapper.eq("password", userPwd.getPassword());
User res = userMapper.selectOne(queryWrapper);
// 判断密码是否正确
if (!bCryptPasswordEncoder.matches(userParam.getPassword(), userPwd.getPassword())) {
return Result.error("-1", "密码错误");
}
if (res == null) {
return Result.error("-1", "用户名或密码错误");
}
// 生成token
String token = TokenUtils.genToken(res);
//res.setToken(token);
//组装cookie
final ResponseCookie responseCookie = ResponseCookie
.from("token", token)
//.secure(true)
.httpOnly(true)
.path("/")
.maxAge(60 * 60 * 24 * 7) //7天有效期
.sameSite("Lax")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
res.setPassword("");
return Result.success(res);
}
//注销
@PostMapping("/logout")
public Result<?> logOut(HttpServletResponse response){
if(request.getCookies()==null){
return Result.error("-1","未登录");
}
final ResponseCookie responseCookie = ResponseCookie
.from("token", "")
//.secure(true)
.httpOnly(true)
.path("/")
.maxAge(0)
.sameSite("Lax")
.build();
response.addHeader(HttpHeaders.SET_COOKIE, responseCookie.toString());
return Result.success();
}
13 文件上传
13.1 普通文件上传
@RestController
@RequestMapping("/file")
public class FileController extends BaseController {
@Value("${server.port}")
private String port;
@Value("${file.ip}")
private String ip;
/**
* 上传接口
*
* @param file
* @return
* @throws IOException
*/
@PostMapping("/upload")
public Result<?> upload(MultipartFile file) throws IOException {
if (file == null) {
return Result.error("-1", "文件为空");
}
String fileType = file.getContentType();
if(fileType==null){
return Result.error("-1", "未知文件格式");
}
if (!fileType.contains("image/")) {
return Result.error("-1", "文件格式上传错误");
}
if(!verifyLoginStatus()){
return Result.error("-1","登录状态有误,请重新登陆!");
}
String originalFilename = file.getOriginalFilename(); // 获取源文件的名称
// 定义文件的唯一标识(前缀)
String fileUUID = IdUtil.fastSimpleUUID();
String rootFilePath = System.getProperty("user.dir") + "/files/" + fileUUID + "_" + originalFilename; // 获取上传的路径
File rootFile = new File(rootFilePath);
if (!rootFile.getParentFile().exists()) {
rootFile.getParentFile().mkdirs();
}
FileUtil.writeBytes(file.getBytes(), rootFilePath); // 把文件写入到上传的路径
return Result.success("/file/" + fileUUID); // 返回结果 url
}
/**
* 下载接口
*
* @param fileUUID
* @param response
*/
@GetMapping("/{fileUUID}")
public Result<?> getFiles(@PathVariable String fileUUID, HttpServletResponse response) {
OutputStream os; // 新建一个输出流对象
String basePath = System.getProperty("user.dir") + "/files/"; // 定于文件上传的根路径
List<String> fileNames = FileUtil.listFileNames(basePath); // 获取所有的文件名称
String fileName = fileNames.stream().filter(name -> name.contains(fileUUID)).findAny().orElse(""); // 找到跟参数一致的文件
try {
if (StrUtil.isNotEmpty(fileName)) {
response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
response.setContentType("application/octet-stream");
response.setHeader("cache-control","max-age=5184000");
byte[] bytes = FileUtil.readBytes(basePath + fileName); // 通过文件的路径读取文件字节流
os = response.getOutputStream(); // 通过输出流返回文件
os.write(bytes);
os.flush();
os.close();
}
} catch (Exception e) {
return Result.error("-1","文件下载失败");
}
return Result.success();
}
}
13.2 头像上传
此类上传需记录url到数据库
@PostMapping("/upload_avatar")
public Result<?> upload(MultipartFile file) throws IOException {
if (file == null) {
return Result.error("-1", "文件为空");
}
String fileType = file.getContentType();
if (fileType == null) {
return Result.error("-1", "未知文件格式");
}
if (!fileType.contains("image/")) {
return Result.error("-1", "文件格式上传错误");
}
if (!verifyLoginStatus()) {
return Result.error("-1", "登录状态有误,请重新登陆!");
}
String originalFilename = file.getOriginalFilename(); // 获取源文件的名称
// 定义文件的唯一标识(前缀)
String fileUUID = IdUtil.fastSimpleUUID();
String rootFilePath = System.getProperty("user.dir") + "/files/" + fileUUID + "_" + originalFilename; // 获取上传的路径
File rootFile = new File(rootFilePath);
if (!rootFile.getParentFile().exists()) {
rootFile.getParentFile().mkdirs();
}
FileUtil.writeBytes(file.getBytes(), rootFilePath); // 把文件写入到上传的路径
User user = getUserFromCookie();
Profile profile = profileMapper.selectById(user.getId());
profile.setAvatar("/file/" + fileUUID);
profileMapper.updateById(profile);
return Result.success("/file/" + fileUUID); // 返回结果 url
}
标签:return,springboot,private,token,自用,Result,轮子,public,String
From: https://www.cnblogs.com/fordsonliuzhiwen/p/17001821.html