最近刚结束视频办理系统1.0的项目,做个大致总结。
在本项目中,我参与了数据库表字段设计、后台搭建、打包部署等工作。
主要对后台搭建以及编写接口使用到的方法等内容进行总结。
1、创建一个springboot项目,设计包结构。共6个部分,分别是:config配置包、controller控制器交互类、entity实体类、mapper数据库映射类、service和serviceImpl业务逻辑实现层、utils通用工具包。
2、在pom.xml文件中进行依赖配置
<?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>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>videoProcessing</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
<name>videoProcessing</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.5.RELEASE</version>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>
<!--mybatis-plus-->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.3.2</version>
</dependency>
<!--fileupload-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<!--devtools-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!--生产配置元数据-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<!--jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<!--aspect-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.2</version>
</dependency>
<!--lang-->
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.7</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.15</version>
</dependency>
<!--swagger-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.7.0</version>
</dependency>
<!--swagger-ui-->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.7.0</version>
</dependency>
<!--阿里云 sms sdk 依赖-->
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>alibabacloud-dysmsapi20170525</artifactId>
<version>2.0.23</version>
</dependency>
<!--jacob-1.18-M2-->
<dependency>
<groupId>com.hynnet</groupId>
<artifactId>jacob</artifactId>
<version>1.18</version>
</dependency>
<!--swt-->
<dependency>
<groupId>org.eclipse</groupId>
<artifactId>swt</artifactId>
<version>3.3.0-v3346</version>
</dependency>
<!-- rsa工具 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.52</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.52</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
<!-- 添加访问静态资源文件 -->
<!-- 代码的作用是让src/main/webapp在编译的时候在resoureces路径下也生成webapp的文件 -->
<resources>
<resource>
<directory>src/main/webapp</directory>
<targetPath>META-INF/resources</targetPath>
<includes>
<include>**/**</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>
3、在application.properties中配置相关信息
server.port=9080
#spring.servlet.multipart.max-file-size=10240MB
#uploadImgPath = D\:\\threevr\\pictures\\
spring.web.resources.static-locations= classpath:/META-INF/resources/,classpath:/resources/,classpath:/static/,classpath:/public/
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/video_processing?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.global-config.db-config.id-type=auto
spring.redis.database=6
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.max-wait=-1ms
spring.redis.jedis.pool.min-idle=0
spring.redis.lettuce.shutdown-timeout=100ms
spring.jackson.time-zone=GMT+8
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
4、业务功能模块
(1)用户注册、登录
- 注册模块:使用MD5加密加盐,将用户名、密码等信息存入数据库。
- 登录模块:使用shiro+jwt+redis生成token校验,在用户登录时保存token信息,并实现用户在注册时使用的MD5加密加盐对应解密,实现登录。
(2)用户退出
用户退出后,将redis中的token以及用户名信息删除。(此功能已开发,前端暂未调用,无法检验是否正确)
(3)获取用户信息
(4)修改用户密码
此功能也需要借助MD5工具
public R<?> modifyPwdByUser(@RequestParam(name = "userName")String userName,
@RequestParam(name = "password") String password,
@RequestParam(name = "salt") String salt){
password = SaltMD5Util.generateSaltPassword(password);
User user = new User();
salt = SaltMD5Util.generateSaltPassword(user.getSalt());
int modify = userService.modifyPwd(userName,password,salt);
System.out.println("modify:"+modify);
if (modify!=0){
return R.OK("修改成功!");
}else
return R.error("修改失败,请重试!");
}
(5)修改用户头像
public static final int AVATAR_MAX_SIZE = 10 *1024 * 1024;
public static final List<String> AVATAR_TYPE = new ArrayList<>();
static {
AVATAR_TYPE.add("image/jpeg");
AVATAR_TYPE.add("image/png");
AVATAR_TYPE.add("image/bmp");
AVATAR_TYPE.add("image/gif");
}
@ApiOperation(value = "修改用户头像")
@PostMapping("/modifyImg")
@ApiImplicitParams({
@ApiImplicitParam(name = "userName",value = "用户名称",paramType = "query",dataType = "string")
})
public void modifyImg(@RequestParam(value = "file")MultipartFile file, HttpServletRequest request, HttpServletResponse response){
if (file.isEmpty()){
R.error("文件为空");
}
if (file.getSize() > AVATAR_MAX_SIZE){
R.error("文件超出限制");
}
String contentType = file.getContentType();
if (!AVATAR_TYPE.contains(contentType)){
R.error("文件类型不支持");
}
String url = request.getContextPath();
System.out.println("url:"+url);
String parent = request.getSession().getServletContext().getRealPath("/avatar/");
File dir = new File(parent);
if (!dir.exists()){
dir.mkdirs();
}
String imgName = file.getOriginalFilename();
System.out.println("parent:"+parent);
File dest = new File(dir,imgName);
try {
file.transferTo(dest);
}catch (IOException e){
R.error("文件读写异常");
}
String userName = request.getParameter("userName");
String imgUrl = parent + imgName;
System.out.println("imgUrl:"+imgUrl);
User user = new User();
imgName = imgName.substring(0,imgName.lastIndexOf("."));
System.out.println("imgName:"+imgName);
//user.setAvatar(imgUrl);
user.setAvatar(imgName);
int modify = userService.ModifyImg(userName,imgName);
if (modify!=0){
R.OK("上传成功");
}
}
(6)根据网办中心获取业务
此功能使用到了string字符串的相关用法
// 字符串userName的内容是否等于“网办中心”
"网办中心".equals(userName)
// 字符串userName的内容是否包括“网办中心”
userName.contains("网办中心")
(7)提交日志信息
此功能需要的是一个请求体,使用注解@RequestBody。在存入数据库的过程中,有几个string类型的字段需要从10进制转换成16进制,并返回16进制字符串。
//StringUtils.leftPad(Integer.toHexString(要转换的值),转换长度,转换后的长度不足,则头部补0)
List<BusinessForm> list = new ArrayList<>();
list = businessMapper.selectList(qw);
String businessId = StringUtils.leftPad(Integer.toHexString(list.get(0).getId()), 3, "0");
(8)分页查询日志
此功能有个使用自带分页查询,有个问题就是总数为0,在这里只能通过getRecords().size()来获取了。
@ApiOperation(value = "pageLogOld",notes = "根据条件获取日志信息")
@PostMapping("/pageLogOld")
//@RequiresAuthentication
public R<?> pageLogOld(@RequestBody JSONObject jsonObject) {
//Page<MatterLog> page = matterLogService.pageLogOld(jsonObject);
String matterCode = jsonObject.getString("matterCode");
String businessName = jsonObject.getString("businessName");
String networkCenter = jsonObject.getString("networkCenter");
String customName = jsonObject.getString("customName");
String applyName = jsonObject.getString("applyName");
String startTime = jsonObject.getString("startTime");
String endTime = jsonObject.getString("endTime");
QueryWrapper<MatterLog> qw = new QueryWrapper<>();
if (matterCode!=null && (!"".equals(matterCode))){
qw.eq("matter_code",matterCode);
}
if (businessName!=null && (!"".equals(businessName))){
qw.eq("business_name",businessName);
}
if (networkCenter!=null && (!"".equals(networkCenter))){
qw.eq("network_center",networkCenter);
}
if (customName!=null && (!"".equals(customName))){
qw.eq("custom_name",customName);
}
if (applyName!=null && (!"".equals(applyName))){
qw.eq("apply_name",applyName);
}
if (startTime!=null && (!"".equals(startTime))){
qw.ge("create_time",startTime);
}
if (endTime!=null && (!"".equals(endTime))){
qw.le("create_time",endTime);
}
//当前页数
Long pageNum = Long.valueOf(String.valueOf(jsonObject.get("pageNum")));
//每页显示数
Long pageSize = Long.valueOf(String.valueOf(jsonObject.get("pageSize")));
//进行分页查询-可跟条件
Page<MatterLog> urf = matterLogmapper.selectPage(new Page<>(pageNum, pageSize), qw);
//System.out.println("urf:"+urf);
return R.OK(""+urf.getRecords().size(),urf);
}
5、配置类
(1)shiro+jwt+redis生成token,完成登录校验。(可以看之前的学习笔记二)
(2)统一返回接口格式以及返回情况的枚举类
import lombok.Data;
import java.io.Serializable;
@Data
public class R<T> implements Serializable {
private static final long serialVersionUID = 1l;
//成功标志
private Boolean success=true;
//返回代码
private Integer code;
//返回处理消息
private String message;
//返回数据对象 data
private T data;
//时间戳
private long timestamp = System.currentTimeMillis();
public R(){}
public R<T> success(String message) {
this.message = message;
this.code = 200;
this.success = true;
return this;
}
public static R<Object> ok(){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
return r;
}
public static R<Object> ok(String msg){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(msg);
return r;
}
public static R<Object> ok(Object data){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
r.setData(data);
return r;
}
public static<T> R<T> OK(){
R<T> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
return r;
}
public static<T> R<T> OK(T data){
R<T> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(ResultEnum.SUCCESS.getMessage());
r.setData(data);
return r;
}
public static<T> R<T> OK(String msg,T data){
R<T> r = new R<>();
r.setSuccess(ResultEnum.SUCCESS.getSuccess());
r.setCode(ResultEnum.SUCCESS.getCode());
r.setMessage(msg);
r.setData(data);
return r;
}
/**
* 请求失败
*/
public static R<Object> error(){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.FAILED.getSuccess());
r.setCode(ResultEnum.FAILED.getCode());
r.setMessage(ResultEnum.FAILED.getMessage());
return r;
}
public static R<Object> error(String msg){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.FAILED.getSuccess());
r.setCode(ResultEnum.FAILED.getCode());
r.setMessage(msg);
return r;
}
public R<T> error500(String message) {
this.message = message;
this.code = 500;
this.success = false;
return this;
}
/**
* 请求无权限
*/
public static R<Object> unanthorized(){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.UNAUTHORIZED.getSuccess());
r.setCode(ResultEnum.UNAUTHORIZED.getCode());
r.setMessage(ResultEnum.UNAUTHORIZED.getMessage());
return r;
}
/**
* 请求无权限
*/
public static R<Object> unanthorized(String message){
R<Object> r = new R<>();
r.setSuccess(ResultEnum.UNAUTHORIZED.getSuccess());
r.setCode(ResultEnum.UNAUTHORIZED.getCode());
r.setMessage(message);
return r;
}
public R<T> setResult(ResultEnum resultEnum){
R<T> r = new R<>();
r.setSuccess(resultEnum.getSuccess());
r.setCode(resultEnum.getCode());
r.setMessage(resultEnum.getMessage());
return r;
}
public R<T> success(Boolean status){
this.setSuccess(status);
return this;
}
public R<T> code(Integer code){
this.setCode(code);
return this;
}
public R<T> message(String message){
this.setMessage(message);
return this;
}
}
import lombok.Getter;
import lombok.ToString;
@Getter
@ToString
public enum ResultEnum {
SUCCESS(true,200,"操作成功"),
FAILED(false,300,"操作失败"),
NO_OPERATOR_AUTH(false,301,"无操作权限"),
DATA_ERROR(false,302,"数据错误"),
DATA_NO_EXIST(false,303,"数据不存在"),
UNAUTHORIZED(false,401,"用户认证失败");
private final Boolean success;
private final Integer code;
private final String message;
ResultEnum(Boolean success,Integer code,String message){
this.success=success;
this.code=code;
this.message=message;
}
}
(3)swagger配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
@Configuration
@EnableSwagger2
public class SwaggerConfig {
@Bean
public Docket createRestApi() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(this.apiInfo())
.select()
.apis(RequestHandlerSelectors.basePackage("com.example.videoprocessing.controller"))
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("videoProcessing APIs")
.description("API")
.termsOfServiceUrl("url")
.version("1.0")
.build();
}
}
6、通用工具包utils
(1)JwtUtil(可看之前的学习笔记二)
(2)oConvertUtils(定义随机数)
import lombok.extern.slf4j.Slf4j;
import java.util.Random;
@Slf4j
public class oConvertUtils {
/**
* 随机数
* @param place 定义随机数的位数
*/
public static String randomGen(int place) {
String base = "qwertyuioplkjhgfdsazxcvbnmQAZWSXEDCRFVTGBYHNUJMIKLOP0123456789";
StringBuffer sb = new StringBuffer();
Random rd = new Random();
for(int i=0;i<place;i++) {
sb.append(base.charAt(rd.nextInt(base.length())));
}
return sb.toString();
}
}
(3)RedisUtil(可看之前的学习笔记二)
(4)SaltMD5Util(MD5加密加盐)
import org.apache.commons.codec.binary.Hex;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
public class SaltMD5Util {
/**
* @Description 生成普通的MD5密码
*/
public static String MD5(String input){
MessageDigest md5 = null;
try {
//生成普通的MD5密码
md5 = MessageDigest.getInstance("MD5");
}catch (NoSuchAlgorithmException e){
return "check jdk";
}catch (Exception e){
e.printStackTrace();
return "";
}
char[] charArray = input.toCharArray();
byte[] byteArray = new byte[charArray.length];
for (int i = 0; i < charArray.length; i++)
byteArray[i] = (byte) charArray[i];
byte[] md5Bytes = md5.digest(byteArray);
StringBuffer hexValue = new StringBuffer();
for (int i = 0; i < md5Bytes.length; i++){
int val = ((int) md5Bytes[i]) & 0xff;
if (val < 16)
hexValue.append("0");
hexValue.append(Integer.toHexString(val));
}
return hexValue.toString();
}
/**
* @Description 生成盐和加盐后的MD5码,并将盐混入到MD5码中,对MD5密码进行加强
*/
public static String generateSaltPassword(String password) {
Random random = new Random();
//生成一个16位的随机数,即盐
/**
* 此处的盐也可以定义成一个系统复杂的常量,而不是非要靠随机数,两种方式均可
* 盐加密 : salt的字符串是随意打的,目的是把MD5加密后的再次加密变得复杂
*/
StringBuilder stringBuilder = new StringBuilder(16);
stringBuilder.append(random.nextInt(99999999)).append(random.nextInt(99999999));
int len = stringBuilder.length();
if (len < 16) {
for (int i = 0; i< 16 - len; i++){
stringBuilder.append("0");
}
}
//生成盐
String salt = stringBuilder.toString();
//将盐加到明文中,生成新的MD5码
password = md5Hex(password + salt);
//将盐混到新生成的MD5码中,方便后期校验明文和密文
char[] cs = new char[48];
for (int i = 0; i < 48; i+= 3){
cs[i] = password.charAt(i / 3 * 2);
char c = salt.charAt(i / 3);
cs[i + 1] = c;
cs[i + 2] = password.charAt(i / 3 * 2 + 1);
}
return new String(cs);
}
/**
* @Description 验证明文和加盐后的MD5码是否匹配
*/
public static boolean verifySaltPassword(String password,String md5){
//先从MD5码中取出之前加的盐和加盐后生成的MD5码
char[] cs1 = new char[32];
char[] cs2 = new char[16];
for (int i = 0; i < 48; i += 3){
cs1[i / 3 * 2] = md5.charAt(i);
cs1[i / 3 * 2 + 1] = md5.charAt(i + 2);
cs2[i / 3] = md5.charAt(i + 1);
}
String salt = new String(cs2);
//比较二者是否相同
return md5Hex(password+salt).equals(new String(cs1));
}
/**
* @Description 生成MD5密码
*/
private static String md5Hex(String src) {
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
byte[] bs = md5.digest(src.getBytes());
return new String(new Hex().encode(bs));
}catch (Exception e){
return null;
}
}
public static void main(String args[]){
//原密码
String password = "123456";
System.out.println("明文密码: "+password);
//MD5加密后的密码
String MD5Password = MD5(password);
System.out.println("普通MD5加密密码: "+MD5Password);
//获取加盐后的MD5密码
String SaltPassword = generateSaltPassword(password);
System.out.println("加盐后的密码: "+SaltPassword);
System.out.println("加盐后的密码和明文密码是否是同一字符串: "+verifySaltPassword(password,SaltPassword));
}
}
7、项目打包部署
(1)项目打包形式是war包。在pom.xml内定义war形式,在右侧Maven栏中,找到项目的生命周期,依次双击“clean”、“package”;在左侧项目栏中可看到target包。
(2)部署:放于tomcat9.0.50的webapp文件夹内,修改server.xml文件内容,启动“bin”目录下的“startup.bat”。