云项目是现在工作室最常见的一种项目开发形式了,但是对于码农来说,云项目只是知道,但是如何部署,开发,配置环境等操作,是否能拿得出来呢?这是一个问题,最起码对博主来说,开发五六年了,还是第一次自己从框架到项目部署完整的操作呢,下面跟着博主的节奏,开始刷野吧!
第一步,项目框架搭建
项目框架的搭建,其实在开发中还是很少涉及的,主要是对于项目技术的选型以及对于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 {/*** 加载分页插件*/@Beanpublic 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 IDprivate String jti;//用户idprivate 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());@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {//获取当前请求的uriString 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;}//否:获取请求头中携带的tokenString 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;@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return new ArrayList<>();}@Overridepublic String getPassword() {return user.getPassword();}@Overridepublic String getUsername() {return user.getName();}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic 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 {@Autowiredprivate UserMapper userMapper;@Autowiredprivate JWTAuthenticationFilter jwtAuthenticationFilter;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic 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);};}@Beanpublic AuthenticationProvider authenticationProvider() {DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();authenticationProvider.setUserDetailsService(userDetailsService());authenticationProvider.setPasswordEncoder(passwordEncoder());authenticationProvider.setHideUserNotFoundExceptions(false);return authenticationProvider;}@Beanpublic AuthenticationManager authenticationManager() {return new ProviderManager(authenticationProvider());}@Beanpublic 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 headerJWSHeader jwsHeader = new JWSHeader.Builder(JWSAlgorithm.HS256).type(JOSEObjectType.JWT).build();//准备JWS payloadclaims.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
在防护墙中开发端口,然后在阿里云安全组中添加开放端口即可使用