[网站]SpringBoot 企业级开发笔记
本文最后更新于 330 天前,其中的信息可能已经有所发展或是发生改变。

注意:因为是学习,所以在没有发现问题前,可能会存在错误,请仔细甄别。

待补充

使用到的库

lombok库

Lombok Java类库,使用注解的形式自动生成构造器、getter、setter、equals、hashcode、toString等方法
https://mvnrepository.com/artifact/org.projectlombok/lombok
https://www.bilibili.com/video/BV1m84y1w7Tb?p=121

项目文档

请求参数

响应参数

  • 响应数据类型:application/json

JavaWeb(Spring Boot)

controller

Spring Boot中的 6 种API请求参数读取方式

@RequestParam(required = false) 表示参数不是必须的
@PathVariable
@RequestBody
@RequestHeader

service

serviceImpl

mapper

登录认证

会话技术:
会话:用户打开浏览器,访问web服务器的资源,会话建立,知道有一方断开连接,会话结束。在一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据。
会话跟踪方案:客户端会话跟踪技术:Cookie,服务端会话跟踪技术:Session,令牌技术

拦截器(Filter)

使用implements声明多个接口,包含初始化(一次),放行(多次),销毁(一次)。

过滤器(Interceptor)

Spring Boot项目部署

属性配置方式

改变运行端口:
1. 修改 application.propertiesapplication.yml 中的 server.port.端口号 (打包后麻烦,且交给客户后更加麻烦)
2. 使用 --键=值 的方式 如:java -jar 文件名.jar --server.port =9999 这样就可以将运行端口改为 9999
3. 修改环境变量: 在用户变量下,新建一个变量名为 server.port ,变量值为 9999 (务必注意,修改完环境变量后,记得重新打开窗口)
4. 外部文件配置方式:在jar包所在目录下,新建一个 application.yml ,将需要改变的配置写入即可。
配置优先级:项目中 resources 目录下的 application.yml < Jar包所在目录的 application.yml < 操作系统环境变量 < 命令行参数

多环境开发-Pofiles

# 如何分隔不同环境配置?
---
# 如何指定哪个环境的配置文件生效?
spring:
  profiles:
    active: 环境名称
# 如何指定哪些配置属于哪个环境?
spring:
  config:
    activate:
      on-profile: 环境名称

---
# 通用配置,指定激活的配置文件
spring:
  profiles:
    active: dev # 指定激活 dev 环境
# 项目访问路径(虚拟路径)
server:
  servlet:
    context-path: /aaa
---
# 开发环境配置
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 8081
---
# 测试环境配置
spring:
  config:
    activate:
      on-profile: test
server:
  port: 8082
---
# 生产环境配置
spring:
  config:
    activate:
      on-profile: pro
server:
  port: 8083

很显然,文件内容太多,看的眼花缭乱。因此还有一种方法。

我们可以分别为环境设置对应的yml文件如:application-dev.ymlapplication-test.ymlapplication-pro.yml ,只需要在 application.yml 中使用 spring.profiles.active.dev 即可激活

根据上面的方法,我们可以类推出,把配置信息配置到不同的配置文件中如:application-devServer.ymlapplication-devDB.yml 等等。
如何激活:spring.profiles.active:dev.group."dev":devServer,devDB

吐槽:非大型项目已选第一个就行,这配置方法多种多样,去学Vue了。。

Vue

跨域(CORS):指由于浏览器同源策略限制,向不同源(不同协议、不同域名、不同端口)发送ajax请求会失败

更改配置文件

# request.js
import axios from 'axios'
const baseURL = '/api'
const instance = axios.create({baseURL})
//以下略

# vite.config.js 
server: {
    proxy: {
      '/api': {
        target: 'http://localhost:8080',
        changeOrigin: true,
        rewrite: (path) => path.replace(/^\/api/, '')
      }
    }
  }

如果访问 http://localhost:5173/api 则会改为 http://localhost:8080/

Vue Router

详情:安装 | Vue Router

npm install vue-router@4

导航守卫

详情:导航守卫 | Vue Router

Pinia

*请在此处访问开始 | Pinia (vuejs.org)快速开始 | pinia-plugin-persistedstate (prazdevs.github.io)来进行安装,这里不再赘述。

在stores文件夹下新建index.js文件和modules文件夹。将pinia进行独立维护。

// index.js
import { createPinia } from 'pinia'
// 引入pinia插件 用于数据持久化
import { createPersistedState } from 'pinia-plugin-persistedstate'

const pinia = createPinia();
const persist = createPersistedState();
pinia.use(persist)

export default pinia

// 导出所有的store
export * from './modules/(文件名)'

使用

// 其他地方直接调用 例:import {useUserStore} from '@/stores'

挑重点,可恶,看的头大,以下根据黑马程序员的大事件案例进行梳理

前端使用vue、vite、element-plus、unplugin-vue-components、unplugin-auto-import、pinia、pinia-plugin-persistedstate、vue-router、axios、eslint、prettier、sass。这里默认你已经安装好。

// 这里我们在src目录下创建一个stores/modules文件夹,里面再创建一个token.js文件,让pinia来存储
// token.js
import { ref } from 'vue'
import { defineStore } from 'pinia'

/* 
    第一个参数:名字,唯一性
    第二个参数:函数,函数的内部可以定义状态的所有内容

    返回值: 函数
*/
export const useTokenStore = defineStore('token', () => {
    // 定义状态
    // 1.响应式变量
    const token = ref('')

    // 2.定义一个函数,修改token的值
    const setToken = (newToken) => {
        token.value = newToken
    }

    // 3.定义一个函数,清除token的值
    const removeToken = () => {
        token.value = ''
    }

  return { token, setToken, removeToken }
},{
  persist: true, // 是否持久化
})

其次是请求,我们src目录下创建一个utils文件夹,再创建一个request.js文件

import axios from 'axios'
import { ElMessage } from 'element-plus'

// 公共地址
const baseURL = 'http://localhost:8080'
const instance = axios.create({baseURL})

// 导入用户token,便于存储
import {useTokenStore} from '@/stores/token.js'

// 请求拦截器
instance.interceptors.request.use(
    (config)=>{
        // 请求前回调
        const tokenStore = useTokenStore();
        // 尝试将token添加到请求头
        // 如果token存在,则添加到请求头
        if(tokenStore.token){
            config.headers.Authorization = tokenStore.token
        }
        return config
    },
    (err)=>{
        // 将异步的状态改为失败的状态
        return Promise.reject(err)
    }
)


// 导入路由便于跳转
import router from '@/router'

// 响应拦截器
instance.interceptors.response.use(
    (result)=>{
        // 响应成功回调
        if(result.data.code === 0){
             // 将异步的状态改为成功的状态
            return result.data
        }
        // 其他状态码
        ElMessage.error(result.data.msg ? result.data.msg : '服务器错误,请通知管理员')
        // 将异步的状态改为失败的状态
        return Promise.reject(result.data)
    },
    (err)=>{
        // 响应错误回调
        // 1.获取错误信息
        const {response} = err
        // 2.判断是否有响应
        if(response){
            // 3.判断状态码
            switch(response.status){
                case 401:
                    // 4.提示用户
                    ElMessage.error('请先登录')
                    // 5.跳转到登录页
                    router.push('/login')
                    break
                case 403:
                    // 6.提示用户
                    ElMessage.error('没有权限')
                    break
                case 404:
                    // 7.提示用户
                    ElMessage.error('请求地址错误')
                    break
                default:
                    // 8.提示用户
                    ElMessage.error('服务器错误,请通知管理员')
                    break
            }
        }else{
            // 9.提示用户
            ElMessage.error('网络错误')
        }
        // 将异步的状态改为失败的状态
        return Promise.reject(err)
    }
)

export default instance;

其次是api,我们src目录下创建一个api文件夹,再创建一个category.js文件

/**
 * 分类模块接口列表
 */

import request from '@/utils/request'

// 获取分类列表
export const categoryListService = () =>{
    return request.get('/category')
}
// 新增分类
export const categoryAddService = (categoryData) => {
    return request.post('/category',categoryData)
}
// 删除分类
export const categoryDeleteService = (id) => {
    return request.delete('/category?id='+id)
}
// 更新分类
export const categoryUpdateService = (categoryData) => {
    return request.put('/category',categoryData)
}
// 获取分类详情
export const categoryDetailService = (id) => {
    return request.get('/category/detail?id='+id)
}

封装组件

把对应的组件放到单独的vue文件里,暴露数据以供调试。

打包后的问题

nginx配置

    // 基本用法,其他用法暂时省略
server{
        listen 8081;
        server_name localhost;
        location / {
            root html/dist2;
            index index.html index.htm;
            try_files $uri $uri/ /index.html;
        }
    }

数据库目前处于设计阶段,下面的已经没印象了,估计会删除

数据库设计

用户(user)

  • id(用户ID)
  • user_name(用户名)
  • nick_name(别名)
  • sex(性别)
  • password(密码)
  • email(邮箱)
  • user_pic(用户头像)
  • status(状态)
  • create_time(创建时间)
  • update_time(最近更新时间)

用户权限(user_role)

  • user_id(用户ID)
  • role_id(权限ID)

游戏角色(game_user)

  • id(角色ID)
  • frist_name(性)
  • last_name(名)
  • nick_name(别名)
  • sex(性别)
  • age(年龄)
  • biography(兴趣爱好)
  • update_id(更新用户的id)
  • create_time(创建时间)
  • update_time(最近更新时间)

文章(article)

  • id(文章ID)
  • title(标题)
  • title_name(副标题)
  • content(内容)
  • cover(封面)
  • banner(头图)
  • status(状态 发布|草稿)
  • is_top(是否置顶)
  • view_count(阅读量)
  • category_id(类型ID)
  • create_user_id(创建该分类的用户ID)
  • create_time(创建时间)
  • update_time(最近更新时间)

文章分类(article_tag)

  • article_id
  • tag_id

分类(tag)

文章类型(category)

  • id(类型ID)
  • name(名称)
  • description(简述)
  • status(状态)
  • create_user_id(创建该分类的用户ID)
  • create_time(创建时间)
  • update_time(最近更新时间)

制作商(publisher)

  • id
  • publisher

发行商(franchise)

  • id
  • franchise

权限明细(role)

  • id
  • name(权限名)
  • role_key(角色权限字符串)
  • status(角色状态 正常0|停用1)
  • create_time(创建时间)
  • update_time(最近更新时间)

使用以下代码时,请务必已经观看完此前集数的所有视频

接口文档

将会使用Apifox编写,TODO

以下内容待修复

第15集、注册接口

路径、文件、整体如下:

......
java
|--com.example.galhand_spring
|--|--common
|--|--|--Result.java
|--|--controller
|--|--|--ShopsGameAdminController.java
|--|--dao
|--|--entity
|--|--|--ShopsGameAdmin.java
|--|--|--ShopsGameArticle.java
|--|--|--ShopsGameCategory.java
|--|--exception
|--|--mapper
|--|--|--ShopsGameAdminMapper.java
|--|--service
|--|--|--impl
|--|--|--|--ShopsGameAdminServiceImpl.java
|--|--|--ShopsGameAdminService.java
|--|--utils
|--|--|--Md5Utiles.java
|--GalHandSpringApplication
resources
|--static
|--|--......
|--application.yml

Result.java

package com.example.galhand_spring.common;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

// 无参数的构造方法
// 全参的构造方法
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Result<T> {
    private Integer code; // 业务状态码 0-成功 1-失败
    private String  message; // 提示信息
    private T data; // 响应数据

    // 快速返回操作成功响应结果(带响应数据)
    public static <E> Result<E> success(E data) {return new Result<>(0, "操作成功", data);}
    // 快速返回操作成功响应结果
    public static Result success() {return new Result(0, "操作成功", null);}
    public static Result error(String message) {return new Result(1, message, null);}
}

ShopsGameAdminController.java

package com.example.galhand_spring.controller;


import com.example.galhand_spring.common.Result;
import com.example.galhand_spring.entity.ShopsGameAdmin;
import com.example.galhand_spring.service.ShopsGameAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class ShopsGameAdminController {

    @Autowired
    private ShopsGameAdminService shopsGameAdminService;

    @PostMapping("/register")
    public Result register(String username,String password){
        // 查询用户
        ShopsGameAdmin u = shopsGameAdminService.findByUserName(username);
        if (u==null){
            // 没有占用
            shopsGameAdminService.register(username,password);
            return Result.success();
        }else {
            // 占用
            return Result.error("用户名已被占用");
        }
    }
}

ShopsGameAdmin.java

package com.example.galhand_spring.entity;

import java.time.LocalDateTime;

import lombok.Data;
// 使用lombok 代替get set方法
// pom.xml中引入lombok依赖
@Data
public class ShopsGameAdmin {
    private Integer id; // 后台用户ID
    private String username; // 后台用户名
    private String password; // 后台用户密码
    private String nickname; // 后台用户昵称/别名
    private String email; // 后台用户邮箱
    private String userPic; // 后台用户头像
    private LocalDateTime createTime; // 创建时间
    private LocalDateTime updateTime; // 更新时间
//    private String role; // 角色权限
}

ShopsGameArticle.java

package com.example.galhand_spring.entity;

import lombok.Data;

import java.time.LocalDateTime;

// 使用lombok 代替get set方法
// pom.xml中引入lombok依赖
@Data
public class ShopsGameArticle {
    private Integer id; // 文章ID
    private String title; // 文章标题
    private String titleName; //文章别名
    private String content; // 文那日容
    private String coverImg; // 封面图像
    private String state; // 状态
    private Integer categoryId; // 文章分类
    private Integer createUser; // 创建人ID
    private LocalDateTime createTime; // 创建时间
    private LocalDateTime updateTime; // 更新时间
}

ShopsGameCategory.java

package com.example.galhand_spring.entity;

import lombok.Data;

import java.time.LocalDateTime;

// 使用lombok 代替get set方法
// pom.xml中引入lombok依赖
@Data
public class ShopsGameCategory {
    private Integer id; // 分类主键ID
    private String categoryName; // 分类名称
    private String categoryAlias; // 分类别名
    private Integer createUser; // 创建人ID
    private LocalDateTime createTime; // 创建时间
    private LocalDateTime updateTime; // 更新时间
}

ShopsGameAdminMapper.java

package com.example.galhand_spring.mapper;

import com.example.galhand_spring.entity.ShopsGameAdmin;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

@Mapper
public interface ShopsGameAdminMapper {

    //根据用户名查询用户
    @Select("select * from shops_game_admin where username=#{username}")
    ShopsGameAdmin findByAdminName(String username);

    //添加
    // 注意这里时对应数据字段的
    @Insert("insert into shops_game_admin(username,password,createTime,updateTime)" +
            "values(#{username},#{password},now(),now())")
    // 敬请注意!!!自动生成的代码一定要再看一遍,防止他生成错了
    void add(String username, String password);

}

ShopsGameAdminService.java

package com.example.galhand_spring.service;

import com.example.galhand_spring.entity.ShopsGameAdmin;

public interface ShopsGameAdminService {
    // 根据用户名查询用户
    ShopsGameAdmin findByUserName(String username);

    // 注册
    void register(String username, String password);
}

ShopsGameAdminServiceImpl.java

package com.example.galhand_spring.service.impl;

import com.example.galhand_spring.entity.ShopsGameAdmin;
import com.example.galhand_spring.mapper.ShopsGameAdminMapper;
import com.example.galhand_spring.service.ShopsGameAdminService;
import com.example.galhand_spring.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ShopsGameAdminServiceImpl implements ShopsGameAdminService {
    @Autowired
    private ShopsGameAdminMapper shopsGameAdminMapper;
    @Override
    public ShopsGameAdmin findByUserName(String username) {
        ShopsGameAdmin u = shopsGameAdminMapper.findByAdminName(username);
        return u;
    }

    @Override
    public void register(String username, String password) {
        // 加密
        String md5String = Md5Util.getMD5String(password);
        // 调用 Mapper 添加
        shopsGameAdminMapper.add(username,md5String);
    }
}

Md5Util.java

package com.example.galhand_spring.utils;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class Md5Util {
    /**
     * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合
     */
    protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};

    protected static MessageDigest messagedigest = null;

    static {
        try {
            messagedigest = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException nsaex) {
            System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。");
            nsaex.printStackTrace();
        }
    }

    /**
     * 生成字符串的md5校验值
     *
     * @param s
     * @return
     */
    public static String getMD5String(String s) {
        return getMD5String(s.getBytes());
    }

    /**
     * 判断字符串的md5校验码是否与一个已知的md5码相匹配
     *
     * @param password  要校验的字符串
     * @param md5PwdStr 已知的md5校验码
     * @return
     */
    public static boolean checkPassword(String password, String md5PwdStr) {
        String s = getMD5String(password);
        return s.equals(md5PwdStr);
    }


    public static String getMD5String(byte[] bytes) {
        messagedigest.update(bytes);
        return bufferToHex(messagedigest.digest());
    }

    private static String bufferToHex(byte bytes[]) {
        return bufferToHex(bytes, 0, bytes.length);
    }

    private static String bufferToHex(byte bytes[], int m, int n) {
        StringBuffer stringbuffer = new StringBuffer(2 * n);
        int k = m + n;
        for (int l = m; l < k; l++) {
            appendHexPair(bytes[l], stringbuffer);
        }
        return stringbuffer.toString();
    }

    private static void appendHexPair(byte bt, StringBuffer stringbuffer) {
        char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>>
        // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同
        char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换
        stringbuffer.append(c0);
        stringbuffer.append(c1);
    }

}

关于这部分更加详细的内容请观看第15集

来自
本博客所有文章除特别声明外,均采用署名-非商业性使用-相同方式共享 4.0进行许可,仅供个人学习使用。
上一篇
下一篇