云项目是现在工作室最常见的一种项目开发形式了,但是对于码农来说,云项目只是知道,但是如何部署,开发,配置环境等操作,是否能拿得出来呢?这是一个问题,最起码对博主来说,开发五六年了,还是第一次自己从框架到项目部署完整的操作呢,下面跟着博主的节奏,开始刷野吧!
第一步,项目框架搭建
项目框架的搭建,其实在开发中还是很少涉及的,主要是对于项目技术的选型以及对于pom文件的依赖版本关系的处理,还有就是技术面的广泛程度吧,技术含量对于博主来说,浅方面的还是很简单的。下面我就用我自己开发的一个工作项目来简单举例一下
- 第一是在IDEA 中建立一个springBoot 的项目,具体建立的步骤就不在赘述了,大家相信都会。
- 第二就是建立好项目之后,我们需要引入一些常见的依赖,比如mybaits puls的和lombok等一些依赖。下面贴一下我比较常用的依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.0.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>vote-project</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>vote-project</name>
<description>vote-project</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mybatis puls-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
<exclusions>
<exclusion>
<artifactId>mybatis-spring</artifactId>
<groupId>org.mybatis</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>3.0.3</version>
</dependency>
<!-- msyql-->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入 swagger-->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-api</artifactId>
<version>2.0.2</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.1.3-jre</version>
</dependency>
<dependency>
<groupId>net.sourceforge.nekohtml </groupId>
<artifactId>nekohtml </artifactId>
<version>1.9.22 </version>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- jwt -->
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>9.31</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.26</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
- pom文件的依赖一定要注意版本的区别,我这边使用的是JDK17的版本,对于的版本也需要提高,因为springBoot3.x的版本最低要求是17,所以大家结合综合考虑,在pom依赖中,也添加了安全框架security和jwt来实现登录功能,大家如果不需要可以自行删除
- 引入mybaitpuls功能后,需要进行配置才可正常使用,配置文件如下
package com.example.voteproject.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
@Configuration
public class MybatisPlusConfig {
/**
* 加载分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
@Primary
@Bean
@ConfigurationProperties("mybatis-plus")
public MybatisPlusProperties mybatisPlusProperties() {
return new MybatisPlusProperties();
}
}
这是固定写法,不需要更改
5. 上面配置了security和jwt,所以也附上一份配置代码,仅供参考哈
package com.example.voteproject.config;
import lombok.Data;
import java.util.List;
/**
* jwt实体数据
*/
@Data
public class Claims {
/**
* 主题
*/
private String sub;
/**
* 签发时间
*/
private Long iat;
/**
* 过期时间
*/
private Long exp;
/**
* JWT ID
*/
private String jti;
/**
* 用户id
*/
private String userId;
/**
* 用户名
*/
private String username;
/**
* 用户状态(1:正常;0:禁用)
*/
private String status;
/**
* 用户角色
*/
private List<String> roles;
/**
* 权限列表
*/
private List<String> permissions;
public Claims(String sub, Long iat, Long exp, String jti, String userId, String username, String status, List<String> roles, List<String> permissions) {
this.sub = sub;
this.iat = iat;
this.exp = exp;
this.jti = jti;
this.userId = userId;
this.username = username;
this.status = status;
this.roles = roles;
this.permissions = permissions;
}
public String getSub() {
return sub;
}
public void setSub(String sub) {
this.sub = sub;
}
public Long getIat() {
return iat;
}
public void setIat(Long iat) {
this.iat = iat;
}
public Long getExp() {
return exp;
}
public void setExp(Long exp) {
this.exp = exp;
}
public String getJti() {
return jti;
}
public void setJti(String jti) {
this.jti = jti;
}
public String getUserId() {
return userId;
}
public void setUserId(String userId) {
this.userId = userId;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public List<String> getRoles() {
return roles;
}
public void setRoles(List<String> roles) {
this.roles = roles;
}
public List<String> getPermissions() {
return permissions;
}
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
public static ClaimsBuilder builder() {
return new ClaimsBuilder();
}
public static final class ClaimsBuilder {
//主题
private String sub;
//签发时间
private Long iat;
//过期时间
private Long exp;
//JWT ID
private String jti;
//用户id
private String userId;
//用户名
private String username;
private String status;
//用户角色
private List<String> roles;
private List<String> permissions;
private ClaimsBuilder() {
}
public ClaimsBuilder sub(String sub) {
this.sub = sub;
return this;
}
public ClaimsBuilder iat(Long iat) {
this.iat = iat;
return this;
}
public ClaimsBuilder exp(Long exp) {
this.exp = exp;
return this;
}
public ClaimsBuilder jti(String jti) {
this.jti = jti;
return this;
}
public ClaimsBuilder userId(String userId) {
this.userId = userId;
return this;
}
public ClaimsBuilder username(String username) {
this.username = username;
return this;
}
public ClaimsBuilder status(String status) {
this.status = status;
return this;
}
public ClaimsBuilder roles(List<String> roles) {
this.roles = roles;
return this;
}
public ClaimsBuilder permissions(List<String> permissions) {
this.permissions = permissions;
return this;
}
public Claims build() {
return new Claims(
this.sub,
this.iat,
this.exp,
this.jti,
this.userId,
this.username,
this.status,
this.roles,
this.permissions);
}
}
}
package com.example.voteproject.config;
public interface CommonConstants {
String STATUS_DEL = "-1";
String STATUS_NORMAL = "1";
String STATUS_LOCK = "9";
Long MENU_TREE_ROOT_ID = -1L;
String MENU = "0";
String UTF8 = "UTF-8";
String CONTENT_TYPE = "application/json; charset=utf-8";
String FRONT_END_PROJECT = "pig-ui";
Integer SUCCESS = 0;
Integer FAIL = 1;
String CURRENT = "current";
String SIZE = "size";
String REQUEST_START_TIME = "REQUEST-START-TIME";
int MIN_PAGE = 0;
int MAX_LIMIT = 999;
int DEFAULT_PAGE = 1;
int DEFAULT_LIMIT = 10;
String PAGE_KEY = "page";
String PAGE_LIMIT_KEY = "limit";
String PAGE_SORT_KEY = "sort";
String PAGE_ORDER_KEY = "order";
}
package com.example.voteproject.config;
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.example.voteproject.utils.JWTUtil;
import com.example.voteproject.utils.ResponseEntity;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
import java.util.logging.Logger;
/**
* @author : zy
* <p>
* 自定义jwt全局过滤器
* 1.没有携带token放行
* 2.携带token,将用户信息添加至security上下文中
*/
@Component
public class JWTAuthenticationFilter extends OncePerRequestFilter {
private static final Logger logger = Logger.getLogger(JWTAuthenticationFilter.class.toString());
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//获取当前请求的uri
String uri = request.getRequestURI();
logger.info("请求路径:" + uri);
//判断是否是认证请求路径
//是:直接放行
if (uri.endsWith("/auth/login") || uri.endsWith("/auth/logout") || uri.startsWith("/swagger-ui")
|| uri.endsWith("doc.html") || uri.startsWith("/webjars/css") || uri.startsWith("/webjars/js")
|| uri.startsWith("/v3/api-docs") || uri.startsWith("/favicon.ico")
|| uri.startsWith("/user/login")
|| uri.startsWith("/user/saveOrUpdateEvents")
|| uri.startsWith("/voteMove")
|| uri.startsWith("**/*.html") || uri.endsWith("/webjars/springfox-swagger-ui")
|| uri.startsWith("/swagger-resources")) {
filterChain.doFilter(request, response);
return;
}
//否:获取请求头中携带的token
String authorization = request.getHeader("Authorization");
logger.info("携带authorization:" + authorization);
//判断是否携带token
//否:抛出异常
if (StringUtils.isBlank(authorization)) {
logger.info("未查询到token");
return;
}
String realToken = authorization.replace("Bearer ", "");
//是:校验jwt有效性
ResponseEntity responseE = JWTUtil.verifyToken(realToken);
Claims data = (Claims) responseE.getData();
if (ObjectUtils.isEmpty(data)) {
logger.info("token失效");
return;
}
// 验证token对象是否存在及验证token是否过期
if (ObjectUtils.isEmpty(data)) {
logger.info("token无效或者已经失效");
return;
}
if (responseE.getCode() != 200) {
logger.info("token无效");
return;
}
filterChain.doFilter(request, response);
}
}
package com.example.voteproject.config;
import com.example.voteproject.model.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {
private User user;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return new ArrayList<>();
}
@Override
public String getPassword() {
return user.getPassword();
}
@Override
public String getUsername() {
return user.getName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
package com.example.voteproject.config;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.example.voteproject.mapper.UserMapper;
import com.example.voteproject.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* @author zy
*/
@Configuration
public class SecurityConfig {
@Autowired
private UserMapper userMapper;
@Autowired
private JWTAuthenticationFilter jwtAuthenticationFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public UserDetailsService userDetailsService() {
return username -> {
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getName,username);
User user = userMapper.selectOne(lambdaQueryWrapper,false);
if (user == null) {
throw new UsernameNotFoundException("用户不存在");
}
return new LoginUser(user);
};
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
authenticationProvider.setHideUserNotFoundExceptions(false);
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(authenticationProvider());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests()
.requestMatchers(HttpMethod.OPTIONS).permitAll()
.requestMatchers("/**").permitAll()
.requestMatchers("/auth/login").permitAll()
.requestMatchers("/auth/logout").permitAll()
.requestMatchers("/swagger-ui.html").permitAll()
.requestMatchers("/doc.html").permitAll()
.requestMatchers("/webjars/springfox-swagger-ui/**").permitAll()
.requestMatchers("/swagger-resources").permitAll()
.requestMatchers("/v3/api-docs/**").permitAll()
.requestMatchers("/favicon.ico").permitAll()
.requestMatchers("/error").permitAll()
.anyRequest()
.authenticated();
return http.build();
}
}
package com.example.voteproject.utils;
import com.alibaba.fastjson2.JSON;
import com.example.voteproject.config.Claims;
import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import java.text.ParseException;
import java.util.Date;
import java.util.UUID;
/**
* @author : zy
* JWT工具类
*/
public class JWTUtil {
//密钥
private static final String secret = "xpo1xgnl5ksinxkgu1nb6vcx3zaq1wsxvv";
// 1000 * 60 * 60 * 24 * 1 一天
//过期时间12h,单位毫秒
private static final long EXPIRE = 1000 * 60 * 60 * 12;
// 测试时为1min
// private static final long EXPIRE = 1000 * 60 * 1;
/**
* 创建token
*
* @param claims 用户信息
* @return 令牌
*/
public static String createToken(Claims claims) {
try {
//对密钥进行签名
JWSSigner jwsSigner = new MACSigner(secret);
//准备JWS header
JWSHeader jwsHeader = new JWSHeader
.Builder(JWSAlgorithm.HS256)
.type(JOSEObjectType.JWT)
.build();
//准备JWS payload
claims.setJti(UUID.randomUUID().toString());
claims.setIat(new Date().getTime());
claims.setExp(new Date(System.currentTimeMillis() + EXPIRE).getTime());
Payload payload = new Payload(JSON.toJSONString(claims));
//封装JWS对象
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
//签名
jwsObject.sign(jwsSigner);
return jwsObject.serialize();
} catch (KeyLengthException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
return null;
}
/**
* 验证并获取用户信息
*
* @param token 令牌
* @return 解析后用户信息
*/
public static ResponseEntity verifyToken(String token) {
JWSObject jwsObject;
ResponseEntity response = new ResponseEntity<>();
try {
jwsObject = JWSObject.parse(token);
//HMAC验证器
JWSVerifier jwsVerifier = new MACVerifier(secret);
if (!jwsObject.verify(jwsVerifier)) {
response.setCode(10008).setErrorMsg("token无效");
return response;
}
String payload = jwsObject.getPayload().toString();
Claims claims = JSON.parseObject(payload, Claims.class);
if (claims.getExp() < new Date().getTime()) {
response.setCode(10008).setErrorMsg("token无效");
return response;
}
response.setCode(200).setData(claims).setMessage("解析成功");
return response;
} catch (ParseException | JOSEException e) {
e.printStackTrace();
}
response.setCode(10008).setErrorMsg("token无效");
return response;
}
}
package com.example.voteproject.utils;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.HashMap;
/**
* @author zy
*/
@Schema(description = "返回响应数据")
@Data
public class ResponseEntity<T> {
@Schema(description = "编码")
private int code = 200;
@Schema(description = "基本信息")
private String message = "成功";
@Schema(description = "错误信息")
private String errorMsg = "";
@Schema(description = "返回对象")
private T data;
/**
* 成功状态码
*/
public static final Integer SUCCESS = 200;
/**
* 失败状态码
*/
public static final Integer ERROR = 500;
private static HashMap<Integer, String> ERROR_CODE = new HashMap<Integer, String>() {
{
put(100, "暂无数据");
put(200, "成功");
put(300, "失败");
put(500, "失败状态码");
put(10000, "通用错误");
///用户类
put(10001, "用户名或密码错误");
put(10002, "登录状态已过期");
put(10003, "注册用户已存在");
put(10004, "账号已被锁定,请在一小时后重试");
put(10005, "旧密码错误");
put(10006, "用户名已存在");
put(10007, "ip没有权限");
put(10008, "token无效");
put(10009, "token失效");
///操作权限类
put(20001, "无操作权限");
///参数类
put(30001, "非法参数");
put(30002, "缺少必要参数");
// 数据操作类
put(40001, "添加数据失败");
put(40002, "更新数据失败");
put(40003, "删除数据失败");
put(40004, "添加数据失败,对象已经存在,建议修改或者删除");
put(50001, "不存在的对象");
put(99999, "无任何资源权限");
put(990000, "系统错误");
}
};
public ResponseEntity() {
}
public ResponseEntity(T date) {
this.data = date;
}
public int getCode() {
return code;
}
public ResponseEntity setCode(int code) {
this.code = code;
if (ERROR_CODE.containsKey(code)) {
setMessage(ERROR_CODE.get(code));
}
return this;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public T getData() {
return data;
}
public ResponseEntity setData(T data) {
this.data = data;
return this;
}
public static <T> ResponseEntity<T> def(Class<T> clazz) {
return new ResponseEntity<>();
}
public ResponseEntity<T> ok() {
setCode(200);
return this;
}
public ResponseEntity<T> error(int code) {
setCode(code);
return this;
}
public ResponseEntity<T> message(String message) {
setMessage(message);
return this;
}
public ResponseEntity<T> data(T data) {
setData(data);
return this;
}
public ResponseEntity<T> back(int code, String message, T data) {
setCode(code);
setMessage(message);
setData(data);
return this;
}
public static <T> Boolean isError(ResponseEntity<T> r) {
return !isSuccess(r);
}
public static <T> Boolean isSuccess(ResponseEntity<T> r) {
return ResponseEntity.SUCCESS == r.getCode();
}
}
package com.example.voteproject.enums;
/**
* 返回码
*
* @author zeng
*/
public enum ErrorCode {
SUCCESS(0,"ok","") ,
PARAMS_ERROR(40000,"请求参数错误",""),
NULL_ERROR(40001,"请求参数为空",""),
NO_LOGIN(40100,"未登录",""),
NO_AUTH(40101,"暂无权限访问",""),
SYSTEM_ERROR(50000,"系统内部异常","")
;
//返回码
private final int code;
//操作响应信息
private final String message;
//响应信息的详细描述
private final String description;
//构造函数
ErrorCode(int code, String message, String description) {
this.code = code;
this.message = message;
this.description = description;
}
//get方法
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
public String getDescription() {
return description;
}
}
第二,阿里云配置
阿里云配置添加完成之后,需要去下载安装mysql ,jdk一类的环境,这个方便就不在赘述,其中进行配置端口开放连接外网的步骤,可以执行如下命令
无法连接mysql 服务可以通过使用下面命令解决
iptables -L -n
firewall-cmd --list-all
设置端口允许放行
firewall-cmd --add-port=3306/tcp
firewall-cmd --add-port=3306/tcp --permanent
第三就是项目部署了
- 在idea中进行项目打包成jar包
- 第二是将jar包上传到服务器中
启动jar包
nohup java -jar jar包名
如果打印日志可以使用如下命令
nohup java -jar jar包名 >log.txt&
查看是否运行成功
ps -ef | grep java
在防护墙中开发端口,然后在阿里云安全组中添加开放端口即可使用