极束梦想 - 一个蠢萌蠢萌程序猿的博客 / zh-CN Seale个人小站,Seale博客,极束梦想,极束的梦想,极束の梦想,极束博客 Sun, 24 Jan 2021 10:50:00 +0800 Sun, 24 Jan 2021 10:50:00 +0800 优雅的在Vue3中使用Vuex「TypeScript版」 /archives/105.html /archives/105.html Sun, 24 Jan 2021 10:50:00 +0800 Seale 前言

我们都知道,vuex 的使用在项目中颇为繁琐,因为它有几大概念使得它不能像普通 ref 或者 data 对象一样直接被我们使用,在我们想要更改 vuex 中的数据时,我们需要通过 mutation 来进行提交,获取 vuex 中存储的变量的时候,我们又需要通过 computed 属性来进行声明,试想,如果项目足够庞大,那么我们使用 vuex 的负担就过于重了,这违背了我们使用状态管理的本意。

问题的提出

首先,我们需要明确,我们想要怎么快捷便利地来使用 vuex ,如果你的想法和我如下所示相同,那么恭喜你,或许本文提出的解决方案适合你。

存储

对于 vuex 的存储我们想要实现的类似如下:

this.$m.vuex(name,value);

我们可以在各个组件或者视图内,通过this来直接使用 vuex 进行存储。

读取

对于 vuex 中数据的读取我们想要实现的类似如下:

<template>
<div>{{vuexUser.name}}</div>
</template>

<script lang="ts">
import {defineComponent} from 'vue'
export default defineComponent({
    ...
    methods:{
        // 业务方法
        xxxxx(){
            const flag = this.vuexUser.name? true:false;
            .....
        }
  }
})
</script>

我们能够在模板或者业务逻辑中直接通过 this 直接访问。

解决方案

首先,我们需要在 store 目录下的 index.ts 内加入如下代码

import { createStore } from 'vuex'

export default createStore({
  state: {
    vuexIsLogin: false,        // 当前的登录状态
    vuexTestVar: "这是vuex全局混入的测试变量",
    vuexUser:{
      test: 1,
      name: "测试名字"
    }
  },
  mutations: {
    $changeStore(state: any,payload: any){
      // 判断是否为多层级调用,state中为对象存在的情况,诸如user.info.name = 'xxx'
      const nameArr = payload.name.split('.');
      const len = nameArr.length;
      if (len >= 2){

        let obj = state[nameArr[0]];
        for (let i = 1 ; i < len - 1 ; i++){
          obj = obj[nameArr[i]];
        }
        obj[nameArr[len-1]] = payload.value;
      }else {
        state[payload.name] = payload.value;
      }
    }
  }
})

之后我们在 store 目录下创建一个 ts 脚本文件,在我的项目中我命名为 maxer.mixin.ts ,在这里,我们就需要使用Vue中的一个特性 Mixin(混入)「不明白的同学可以去官方文档查一查,Vue3的官方中文版已经推出了」

/**
 * @作者: Seale
 * @时间: 2021/1/23
 * @版本: V1.0
 * @说明: maxer Vue 全局混入
 * @网站: 
 */

import {mapState} from "vuex";
import store from '@/store/index.ts'
import {App} from 'vue'

// 将定义的state变量key全部加载到全局变量中
const $mStoreKey = store.state ? Object.keys(store.state) : [];
export class Maxer{
    vuex = (name: string, value: any): void=>{
        store.commit('$changeStore', {
            name, value
        })
    }
}

export default<T> (app: App<T>) => {
    // 进行全局混入
    // 将vuex方法挂载到$m中
    // 使用方法为:如果要修改vuex的state中的user.name变量为"x" => this.$m.vuex('user.name','x')
    app.config.globalProperties.$m = new Maxer();
    app.mixin({
        computed: {
            // 将vuex的state中的所有变量,解构到全局混入的mixin中
            ...mapState($mStoreKey)
        }
    })
}

在这里我们使用全局混入,将 vuex 的 state 混入到计算属性中,我们就可以通过类似 this.vuexName 来进行调用,还有一点建议就是,由于采用全局混入的模式将 vuex 中的数据进行混入,所以我们应该用特定的字符来进行标识 vuex 中的数据,我的建议是在 vuex 中变量前加上 vuex_ prefix ,或者类似 vuexVar:'xxx' 来进行声明变量。

之后我们就需要在 main.ts 中进行初始化了。

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import installMaxerStore, {Maxer} from './store/maxer.mixin'
...

// 声明全局组件 防止需要this调用时不能识别类型
declare module '@vue/runtime-core' {
    interface ComponentCustomProperties {
        $m: Maxer;  // 声明全局方法
    }
}
...
const app = createApp(App)
installMaxerStore(app) // 全局混入vuex
app.use(store).mount('#app')

之后我们就可以愉快地使用 vuex 了(在注意变量命名的情况下)。

Vuex 数据持久化

这个时候或许你会发现,当页面进行刷新的时候,vuex 的数据会进行初始化(回到最初的状态),这个是由于 vuex 是运行在内存中的,同样的,它的数据也是存储在内存中,当用户进行刷新页面的操作,所以内存数据会重新进行初始化。

那么我们就可以通过 sessionStorage / localStorage / cookie 来进行数据的持久化存储。

页面加载的时候先读取域中的缓存数据,如果有则覆写。当页面将要刷新前,我们将 vuex 的数据存储到域中。

这里建议使用 sessionStorage ,对于需要长时间持久化的数据再使用 localStorage 或者 cookie。

同样我们在 store 目录下新建一个 ts 脚本。

/**
 * @作者: Seale
 * @时间: 2021/1/24
 * @版本: V1.0
 * @说明: vuex数据持久化,防止F5之后数据消失
 * @网站: 
 */
import {Store} from "vuex";

export default<T> (store: Store<T>): void=>{
    // 不需要持久化的数据存入sessionStorage
    if (sessionStorage.getItem('store')){
        store.replaceState(
            Object.assign(
                {},
                store.state,
                JSON.parse(sessionStorage.getItem('store') as string)
            )
        );
        // 移除sessionStorage中的数据
        sessionStorage.removeItem("store");
    }
    // 页面刷新的时候进行持久化
    window.addEventListener('beforeunload',()=>{
        sessionStorage.setItem("store", JSON.stringify(store.state));
    })
}

对于需要长时间持久化的数据建议自行定义规则进行封装。

之后我们需要在入口文件中进行声明

import { createApp } from 'vue'
import App from './App.vue'
import store from './store'
import installMaxerStore, {Maxer} from './store/maxer.mixin'
import initStorePersistence from './store/store.persistence'
...

// 声明全局组件
declare module '@vue/runtime-core' {
    interface ComponentCustomProperties {
        $m: Maxer;  // 声明全局方法
    }
}
...
const app = createApp(App)
installMaxerStore(app) // 全局混入vuex
initStorePersistence(store) // 初始化持久化vuex
app.use(store).mount('#app')
]]>
0 /archives/105.html#comments /feed/archives/105.html
Typescript入门 /archives/104.html /archives/104.html Wed, 20 Jan 2021 17:51:34 +0800 Seale 前言

Vue3日前已经发布,为了能够更好更快的进行开发,准备学习一下ts,也为了方便我正在写的秋招项目。

NPM安装

使用NPM的可以通过如下命令进行全局安装

npm install -g typescript

示例

我们创建一个新的文件夹,并通过命令行cd进入当前新创建的文件夹下。

之后我们创建一个typescript脚本文件 s1.ts

class Site{
    say():void{
        console.log("我是网站类!")
    }
}
let site = new Site();
site.say()

之后我们通过tsc命令进行编译

tsc s1.ts

之后我们会得到一个js文件

"use strict";
var Site = /** @class */ (function () {
    function Site() {
    }
    Site.prototype.say = function () {
        console.log("我是网站类!");
    };
    return Site;
}());
var site = new Site();
site.say();

之后我们用node命令来执行js

node s1.js

我们可以看到结果

结果

数据类型

  • any:任意类型,声明为 any 的变量可以赋予任意类型的值。
  • number:数字类型
  • string:字符串类型
  • boolean:布尔类型
  • 数组类型:如let x : any[] = [1,"str"]
  • void:用于标识方法返回值的类型,表示该方法没有返回值
  • never:表示不会出现任何值(包括 null 和 undefined)
  • enum:枚举,用于定义数值集合
  • 元组:表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
  • null:表示值缺失
  • undefined:初始化变量为一个未定义的值

基本语法

变量声明

var var1:string = '你好世界!'

条件判断

同其他高级语言

负号运算符

在ts中,我们可以通过负号运算符来更改number类型变量的正负。

num = -num
console.log(`更改为负号后值:${num}`)
当前为2
更改为负号后值:-2

函数中可选参数和默认参数

ts中可以设置当前函数的参数为可选,可选参数必须跟在必需参数后面。

function test (sex:string,type:string = "人", age?:number){
    return age? `一个${age}岁的${sex}${type}`:`一个${sex}${type}`
}
console.log(test("男","人",18));
}

在ts中同时支持类似Java中的可变参数
声明方式同Java

function test(...vars: any[]){
    ......
}

联合类型

我们可以通过|来进行联合类型的声明

var v:string|number;
v="test" //正常
v=1      //正常
v=false  //报错

接口

interface Animal{
    name:string,
    category:string,
    age:number,
    toString: ()=>string
}

let cat:Animal = {
    name: "猫",
    category: "猫科类",
    age: 20,
    toString: ()=>`名为:${cat.name},类型为:${cat.category},年龄为:${cat.age}`
}
console.log(cat.toString());

同时需要注意的是,interface为ts中的特性,并不能转为js脚本。

Typescript 允许接口继承多个接口,使用关键字 extends ,但类只允许单继承。

]]>
0 /archives/104.html#comments /feed/archives/104.html
Vue3新特性之Composition API /archives/vue3-composition-API.html /archives/vue3-composition-API.html Tue, 08 Dec 2020 11:21:55 +0800 Seale 组合式API

在Vue3中,一个重要的变更就是引入了Composition API,它的中文名叫做组合式API。

它是一组低侵入式的、函数式的使得我们能够更灵活地「组合」组件的逻辑。

以下是官网给出的一个例子

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>

<script>
  import { reactive, computed } from 'vue'

  export default {
    setup() {
      const state = reactive({
        count: 0,
        double: computed(() => state.count * 2),
      })

      function increment() {
        state.count++
      }

      return {
        state,
        increment,
      }
    },
  }
</script>

组合式API旨在解决的问题:

  1. 随着功能的增长,复杂组件的代码变得越来越难以阅读和理解。这种情况在开发人员阅读他人编写的代码时尤为常见。根本原因是 Vue 现有的 API 迫使我们通过选项组织代码,但是有的时候通过逻辑关系组织代码更有意义。
  2. 目前缺少一种简洁且低成本的机制来提取和重用多个组件之间的逻辑。

组合式API基础

setup组件选项

一个新的setup组件属性是在创建组件之前执行的,一旦当前组件的prop被解析,那么它将充当Composition API的入口点。

setup中所暴露的任何内容都可以用于组件的其他部分,

以下同样是官方给出的一个例子

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'

// 在我们的组件内
setup (props) {
  let repositories = []
  const getUserRepositories = async () => {
    repositories = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories // 返回的函数与方法的行为相同
  }
}

带ref的响应变量

在Vue3.0中,我们可以通过使用一个新的 ref 函数来使得响应式变量在任何地方起作用。通过这个函数,它会将你的想要定义的响应变量包装为一个对象,你可以通过 variable.value 来进行响应式的更改值。

import { ref } from ‘vue’

const counter = ref(0)

console.log(counter) // { value: 0 }
console.log(counter.value) // 0

counter.value++
console.log(counter.value) // 1

换句话说,ref 对我们的值创建了一个响应式引用。使用引用的概念将在整个组合式 API 中经常使用。

我们可以更改上面的例程,来创建一个响应式的用户仓库。

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  return {
    repositories,
    getUserRepositories
  }
}

setup中注册生命钩子

为了使组合式 API 的特性与选项式 API 相比更加完整,我们还需要一种在 setup 中注册生命周期钩子的方法。这要归功于从 Vue 导出的几个新函数。组合式 API 上的生命周期钩子与选项式 API 的名称相同,但前缀为 on:即 mounted 看起来像 onMounted。

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted } from 'vue'

// in our component
setup (props) {
  const repositories = ref([])
  const getUserRepositories = async () => {
    repositories.value = await fetchUserRepositories(props.user)
  }

  onMounted(getUserRepositories) // on `mounted` call `getUserRepositories`

  return {
    repositories,
    getUserRepositories
  }
}

setup中注册watch

就像我们如何使用 watch 选项在组件内的 user property 上设置侦听器一样,我们也可以使用从 Vue 导入的 watch 函数执行相同的操作。它接受 3 个参数:

  • 一个响应式引用或我们想要侦听的 getter 函数
  • 一个回调
  • 可选的配置选项
import { ref, watch } from 'vue'

const counter = ref(0)
watch(counter, (newValue, oldValue) => {
  console.log('The new counter value is: ' + counter.value)
})

一个官方给出的例子

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'

// 在我们组件中
setup (props) {
  // 使用 `toRefs` 创建对prop的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `prop.user` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  return {
    repositories,
    getUserRepositories
  }
}

toRefs 函数,这是为了确保我们的侦听器能够对 user prop 所做的更改做出反应。

在setup中注册计算属性

与 ref 和 watch 类似,也可以使用从 Vue 导入的 computed 函数在 Vue 组件外部创建计算属性。让我们回到我们的 counter 例子:

import { ref, computed } from 'vue'

const counter = ref(0)
const twiceTheCounter = computed(() => counter.value * 2)

counter.value++
console.log(counter.value) // 1
console.log(twiceTheCounter.value) // 2

同样,我们把它集成到我们的setup中

// src/components/UserRepositories.vue `setup` function
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs, computed } from 'vue'

// in our component
setup (props) {
  // 使用 `toRefs` 创建对 props 的 `user` property 的响应式引用
  const { user } = toRefs(props)

  const repositories = ref([])
  const getUserRepositories = async () => {
    // 更新 `props.user ` 到 `user.value` 访问引用值
    repositories.value = await fetchUserRepositories(user.value)
  }

  onMounted(getUserRepositories)

  // 在用户 prop 的响应式引用上设置一个侦听器
  watch(user, getUserRepositories)

  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(
      repository => repository.name.includes(searchQuery.value)
    )
  })

  return {
    repositories,
    getUserRepositories,
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

写在最后

在大概了解了组合式API后,我深刻感到这种操作能够帮助我们在日常的开发中实现大规模的解耦,特别是一些组件作者在开发大型组件的时候。

当然在日常的开发中,对我们解耦的帮助也是极大的,官方给出的建议是将setup中的逻辑单独提出,之后再引入到需要的组件中去。

我们可以在项目中创建目录 composables

然后提出逻辑内容,但是需要注意需要在function前面加上export default 关键字。

// src/composables/useRepositoryNameSearch.js

import { ref, computed } from 'vue'

export default function useRepositoryNameSearch(repositories) {
  const searchQuery = ref('')
  const repositoriesMatchingSearchQuery = computed(() => {
    return repositories.value.filter(repository => {
      return repository.name.includes(searchQuery.value)
    })
  })

  return {
    searchQuery,
    repositoriesMatchingSearchQuery
  }
}

之后我们可以在对应组件的setup属性内添加以下逻辑

import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import { toRefs } from 'vue'
export default {
  components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
  props: {
    user: { type: String }
  },
  setup (props) {
    const { user } = toRefs(props)
      ...
    const {
      searchQuery,
      repositoriesMatchingSearchQuery
    } = useRepositoryNameSearch(repositories)
      ...
    return {
      // 因为我们并不关心未经过滤的仓库
      // 我们可以在 `repositories` 名称下暴露过滤后的结果
      repositories: repositoriesMatchingSearchQuery,
      searchQuery,
        ...
    }
  },
  data () {
    return {
      filters: { ... }, // 3
    }
  },
  computed: {
    filteredRepositories () { ... }, // 3
  },
  methods: {
    updateFilters () { ... }, // 3
  }
}
]]>
3 /archives/vue3-composition-API.html#comments /feed/archives/vue3-composition-API.html
Java8新特性 /archives/100.html /archives/100.html Sun, 18 Oct 2020 23:06:45 +0800 Seale 前言

目前java已经更新到15了,然鹅,我还在使用8以前的特性,所以为了以后的饭碗,咱还是得自学一下新特性。

Java8新增的几个重要特性

java8中新增了非常多的特性,其中需要主要学习的我认为有以下几个

  1. lambda表达式
  2. 方法引用
  3. Stream API
  4. Optional类
  5. 函数式接口
  6. 默认方法

下面我就来着重介绍一下他们。

Lambda表达式

相信有前端 ES6基础的小伙伴都知道 lambda 是啥,没错,java8中也新增了这个特性。

总的来说lambda表达式就是一个语法糖,它可以极大的减少我们的代码量,但同时或许会给后续的维护工作带来一些困扰。

不同于 ES6 中的 => ,在 Javalambda 的语法格式如下

(parameters) -> expression
or
(parameters) ->{ statements; }

我们可以发现,在 Java 中符号变为了 ->,这个也是需要我们注意的一点。

下面我们来看一个例子,首先,声明一个接口,之后我们用 Lambda 表达式来进行使用。

/**
 * @作者: Seale
 * @时间: 2020/10/15 0:43
 * @说明: 使用lambda表达式
 * @类名: LambdaTest
 */
public class LambdaTest {
    public static void main(String[] args) {
        // 之前的隐式实现
        Itest sumBefore = new Itest() {
            @Override
            public Integer calc(int a, int b) {
                return a+b;
            }
        };
        System.out.println("before:"+sumBefore.calc(12,12));
        // 使用lambda表达式
        Itest sum = (a,b)-> a + b;
        System.out.println("now:"+sum.calc(12,12));

    }
    interface Itest{
        Integer calc(int a,int b);
    }
}

运行结果如下:

before:24
now:24

但值得注意的是,lambda 表达式只能引用标记了final的外层局部变量,反之那么就说明,在lambda表达域之内,我们不能修改在域外的局部变量(可以访问),否则会编译错误。

Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量。

方法引用

方法引用是指通过一个方法的名字来指向它。

方法引用可以使得结构更紧凑和简洁,据我所知,在 BeetlSQL 这个持久化框架(类似于Mybatis)就大量使用到了方法引用来优雅的处理SQL。

方法引用使用的是 :: 一对冒号。

提供的可用来进行方法引用的例子。

/**
 * @作者: Seale
 * @时间: 2020/10/15 0:59
 * @说明: 方法引用
 * @类名: MethodReference
 */
public class MethodReference {
    public static void main(String[] args) {
        ICalc sum = Integer::sum;
        /*ICalc max = Integer::max;*/
        System.out.println(sum.sum(12,15));
    }
    interface ICalc{
        int sum(int a , int b);
        /*int max(int a, int... nums);*/
    }
}

方法引用主要有三类:

  1. 指向静态方法的方法引用,比如 Integer::parseInt
  2. 指向任意类型实例方法的方法引用,比如 String::length
  3. 指向现有对象的实例方法引用 this::hashCode

函数式接口

首先,我们需要知道函数式接口的作用,类似于我们常常使用的注解 @Override ,在我们声明的接口上方,我们需要先声明一个注解 @FunctionalInterface ,那么这个接口就可以被隐式的转换为lambda表达式。

函数式接口有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。

同时,在Java8中,已经内置了几种常用的函数式接口:

  1. 消费型接口 Consumer<T>
  2. 供给型接口 Supplier<T>
  3. 函数型接口 Function<T,R>
  4. 断言型接口 Predicate<T>

消费型接口

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

接收一个参数进行消费,但是无需返回结果。

import java.util.function.Consumer;

/**
 * @作者: Seale
 * @时间: 2020/10/15 0:59
 * @说明: 方法引用
 * @类名: MethodReference
 */
public class MethodReference {
    public static void main(String[] args) {
        Consumer<String> consumer = (str)->System.out.println(str+"\t消费成功");
        consumer.accept("女盆友1号");
        consumer.accept("女盆友2号");
        consumer.accept("女盆友3号");
    }

}
女盆友1号   消费成功
女盆友2号   消费成功
女盆友3号   消费成功

供给型接口

@FunctionalInterface
public interface Supplier<T> {

    /**
     * Gets a result.
     *
     * @return a result
     */
    T get();
}

返回一个结果

public static void main(String[] args) {
        Supplier<String> t1Sl = ()->"男盆友";
        Supplier<String> t2Sl = ()->"女盆友";
        System.out.print(t1Sl.get()+"->");
        System.out.println(t2Sl.get());
    }

函数型接口

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);
    ...
}

同样的,函数型接口需要我们提供一个参数,之后返回一个结果。

public static void main(String[] args) {
        Function<String, Boolean> function = (s) -> {
            System.out.println("当前名字:"+s+"\t执行了工作!");
            return true;
        };
        boolean flag = function.apply("小王");
        System.out.println("执行结果:" + flag);
    }

断言型接口

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    ...
}

传入一个参数,返回成功与否。

下面我们看一个例子。

public static void main(String[] args) {
        Predicate<String> pre1 = String::isEmpty;
        Predicate<String> pre2 = (str)-> str.equals("测试");

        boolean result1 = pre1.test("");
        System.out.println("执行的结果:"+result1);
        boolean result2 = pre2.test("测试1");
        System.out.println("执行的结果:"+result2);
    }

执行的结果:

执行的结果:true
执行的结果:false

默认方法

默认方法是 Java 8 中新增加的特性,简单来说,默认方法就是接口可以有实现方法,而且不需要实现类去实现其中的接口方法。

public interface ITest{
    default void print(){
        System.out.println("测试默认方法");
    }
    // 接口的静态方法
    static void staticPrint(){
        System.out.print("测试的静态方法");
    }
}

Optional 类对象

Optional类是一个可以为null的容器对象,如果存在值那么isPresent()方法会返回true,调用get()方法会返回该对象。

Optional类的引入很好的解决了空指针异常,使得我们在平时的开发中不用再去解决/处理空指针异常。

常用的方法

方法名 描述
static Optional empty() 返回空的Optional实例
boolean equals(Object obj) 判断其他对象是否和Optional中的值相同
T get() 如果在这个Optional中包含这个值,返回值,否则抛出异常:NoSuchElementException
boolean isPresent() 如果值存在则方法会返回true,否则返回 false。
static Optional of(T value) 返回一个指定非null值的Optional。否则抛出空指针异常
static Optional ofNullable(T value) 如果为非空,返回 Optional 描述的指定值,否则返回空的 Optional。
T orElse(T other) 如果存在该值,返回值, 否则返回 other。

表格内容来自菜鸟教程

Stream(超级重要)

Stream是 JAVA 8 中处理集合的抽象概念,它可以让我们以一种声明的方式来处理数据。它用一种类似于SQL语句的方式来操作Java集合、进行集合运算。它是一种对Java集合操作运算的高阶抽象类。

这种操作将要处理的元素集合看作一种流,流在管道中传输,并且可以在管道的节点上进行处理,进行一些集合操作。

特性

  1. 不是数据结构,它不会保存数据。
  2. 不会修改原来对象的数据,它会将操作后的数据保存到另外一个对象中去。
  3. 惰性求值,流在中间处理过程中,只是对操作进行了记录,并不会立即执行,需要等到执行终止的时候才会进行实际的计算。
  4. Pipelining:中间操作都会返回流对象本身,这样多个操作可以串联成一个管道,如同流式风格。这样可以对操作优化,比如延迟执行等。
  5. 内部迭代:在之前对集合遍历都是通过迭代器或者foreach的方式来进行的,显示的在集合外部进行迭代,这叫做外部迭代。而Stream提供了内部迭代的方式,通过访问者模式实现。

什么是Stream

Stream(流)是一个来自数据源的元素队列并且支持聚合操作。

元素是特定类型的独享,形成一个队列,Java中的流对象并不会存储元素,而是按需计算。

数据源是流的来源,可以是集合,数组,I/O通道,产生器等等。

聚合操作类似于SQL语句一样,如filter,map,reduce,find,match,sorted。

具体使用

常用的创建方法

  1. 使用集合下的 stream()parallelStream() 方法来获取流对象。

    public class Stream1 {
       public static void main(String[] args) {
           List<String> strings = Arrays.asList("a","b","c","d","e","f","g");
           Stream<String> stringStream = strings.stream(); //获取一个顺序流
           Stream<String> parallelStream = strings.parallelStream(); //获得一个并行流
           stringStream.forEach(System.out::print);
           System.out.println();
           parallelStream.forEach((s)->{
               System.out.print(s + " ");
           });
       }
    }

    对于以上代码片段有如下结果

    abcdefg
    e d a f c g b 

    我们可以很明显的对比出来,顺序流和并行流的区别。

  2. 使用Stream中的静态方法来构建流对象

    public static void main(String[] args) {
           Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    
           Stream<Integer> stream1 = Stream.iterate(0, (x) -> x + 2).limit(10);
           stream1.forEach((i) -> {
               System.out.print(i + " ");
           }); // 0 2 4 6 8 10 12 14 16 18
    
           System.out.println();
    
           Stream<Double> stream2 = Stream.generate(()-> Math.random() * 10 +1).limit(10);
           stream2.forEach((d) -> {
               System.out.print(d.intValue() + " ");
           }); // 10个随机数
       }

流的中间操作

  1. 筛选与切片操作

    熟悉Python的同学就知道,在Python中可以对字典,集合,元组等数据结构进行一些pythonic风格的操作,其中切片操作最为经典。Java8 也支持类似的操作了。

    filter:过滤流中的某些元素

    limit(n):获取n个元素

    skip(n):跳过n元素

    distinct:通过流中元素的hashCode()和equals()去除重复元素

    public static void main(String[] args) {
           Stream<Integer> stream = Stream.of(6, 15, 100, 258, 150, 0, 1, 2, 5, 4, 8, 9, 6, 3, 6, 9, 6, 165, 4189, 1, 911, 5614, 9, 1, 9, 4, 98, 4, 156, 16, 1);
           Stream<Integer> newStream = stream.filter(i -> i > 5) //获取大于5的元素流对象
                   .distinct();//去重
           newStream.forEach(i-> System.out.print(i + " "));
    
       }

    运行结果

    6 15 100 258 150 8 9 165 4189 911 5614 98 156 16 
  2. 映射

    map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。

    flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

    public static void main(String[] args) {
           List<String> list = Arrays.asList("a,b,c","1,2,3");
    
           Stream<String> s1 = list.stream().map(s -> s.replaceAll(",",""));
           s1.forEach(s -> System.out.print(s+" "));
    
           Stream<String> s3 = list.stream().flatMap(s ->{
              // 将每个元素转换成一个stream
              String[] split = s.split(",");
               return Arrays.stream(split);
           });
           s3.forEach(s->System.out.print(s+" "));
       }
  3. 排序

    sort:自然排序,流中元素需要实现Comparable接口

    sorted(Comparator com):自定义排序

    public static void main(String[] args) {
           List<String> list = Arrays.asList("a","v","b");
           list.stream().sorted().forEach(s -> System.out.print(s+" "));
    
           Student s1 = new Student("张三",18);
           Student s2 = new Student("李四",19);
           Student s3 = new Student("王五",20);
           Student s4 = new Student("陈麻子",17);
           List<Student> students = Arrays.asList(s1,s2,s3,s4);
           students.stream().sorted(Comparator.comparingInt(Student::getAge)).forEach(System.out::println);
       }
  4. 消费

    peek:接收Consumer表达式,没有返回值。

    public static void main(String[] args) {
           List<String> list = Arrays.asList("a","v","b");
           list.stream().sorted().forEach(s -> System.out.print(s+" "));
    
           Student s1 = new Student("张三",18);
           Student s2 = new Student("李四",19);
           Student s3 = new Student("王五",20);
           Student s4 = new Student("陈麻子",17);
           List<Student> students = Arrays.asList(s1,s2,s3,s4);
           students.stream()
                   .peek(o -> o.setAge(o.getAge()+20))
                   .sorted(Comparator.comparingInt(Student::getAge).reversed())
                   .forEach(System.out::println);
           // 在原有的基础上自增20,并进行倒序
       }

流的终止操作

  1. 匹配/聚合

    allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回true,否则返回false

    noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回true,否则返回false

    anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回true,否则返回false

    findFirst:返回流中第一个元素

    findAny:返回流中的任意元素

    count:返回流中元素的总个数

    max:返回流中元素最大值

    min:返回流中元素最小值

  2. 规约(reduce)

    从一组值中生成一个新的值,reduce 函数其实用途非常广泛,作用也比较大,我们举一个累加的例子。

    public static void main(String[] args) {
           List<Integer> list = Arrays.asList(1,2,3,4,5);
           int sum = list.stream().reduce((before,now)->before+now).get();
           System.out.println(sum);
       }
  3. 收集

    collect:接收一个Collector实例,将流中元素收集成为另外一个数据结构。

]]>
0 /archives/100.html#comments /feed/archives/100.html
关于Vue的一些踩坑记录 /archives/vue-some.html /archives/vue-some.html Mon, 12 Oct 2020 23:57:00 +0800 Seale 前言

最近在写一些通用的前端模版,使用到了Vue,但是我们知道Vue远不及Jquery的动效库多,所以我记录一下我在Vue中尝试使用Jquery插件的坑,和一些打包发布的坑。

使用到的技术栈

作为本次记录的项目使用到的技术栈有如下:

  1. Vue2.x (没用3的原因是鄙人太懒了,而且我是主后端的,溜了溜了,而且我是不会告诉你我是打着主后端的幌子偷懒的
  2. Vue-Cli
  3. Vue-Router
  4. Element

正文

问题产生的原因

我想要实现一个由底向上的气泡效果,如果自己用原生实现的话,就要用Javascript来操作canvas,总之,秉承着有轮子就用轮子的思想,就找到了一个Jquery插件,那么问题来了,如何将Jquery和Vue整合到一起呢?

解决方法

那么,首先我们就需要通过npm来将Jquery引入到我们本地项目中

npm install jquery@xxx  //xxx为版本号

由于我的项目是vue cli 4.x创建的,所以我们需要在项目的根目录下(非src目录)创建一个名为vue.config.js的文件

let webpack = require('webpack');

module.exports = {
    configureWebpack:{
        plugins: [
            new webpack.ProvidePlugin({
                $: "jquery",
                jQuery: "jquery",
                jquery: "jquery",
                "window.jQuery": "jquery"
            })
        ]
    }
}

我们输入以上内容,之后保存。

然后这里有个坑来了,由于eslint的检测机制,所以我们需要配置一下我们的EsLint,在项目里打开package.json文件

"eslintConfig": {
    "root": true,
    "env": {
      "node": true,
      "jquery": true
    }

eslintConfig节点下添加jquery。

现在我们在main.js中引入我们的jQuery。

/*引入Jq*/
import 'jquery'

再将我们需要用到的插件压缩为min版,并将其移动到src下的asset目录中的js文件夹中

我们就可以在我们需要的vue模板中绑定元素了。

mounted() {
    $('#babbleBack').circleMagic({
      elem: '#babbleBack',
      radius: 10,
      densety: .2,
      color: 'rgba(255,255,255, .4)',
      clearOffset: .8
    });
  }

测试效果

在这里我已经将一个初版效果放到我的测试服务器上面了,大家可以访问看一看效果。

[button color="danger" icon="fontello fontello-gratipay" url="http:\/\/pro.imsle.com"]访问链接[/button]

值得一提的部署坑

我将项目部署到服务器的过程中也遇到了一些坑,总结如下,首先由vue-cli构建的项目如果事先勾选了router插件,那么它默认的模式为history,这个就将之前的vue链接上的/#/去掉了,同样的这也需要后端服务器的支持,如果部署在Nginx上,我们需要更改一下Nginx的规则,在Vue-router的官方文档上也给出了一些后端服务器的解决方案。

以下针对于Nginx的配置

location / {
  try_files $uri $uri/ /index.html;
}

[button color="success" icon="fontello fontello-gratipay" url="https:\/\/router.vuejs.org\/zh\/guide\/essentials\/history-mode.html#%E5%90%8E%E7%AB%AF%E9%85%8D%E7%BD%AE%E4%BE%8B%E5%AD%90"]访问链接[/button]

]]>
8 /archives/vue-some.html#comments /feed/archives/vue-some.html
重温Redis /archives/redis.html /archives/redis.html Sun, 06 Sep 2020 23:41:00 +0800 Seale redis

简介

Redis是目前使用最光啊发你的内存数据库存储系统之一,它支持很丰富的数据结构,数据持久化,事务,HA(高可用),双机集群系统,主从库。

Redis是Key-Value存储系统,它支持的value类型包括String、List、Set、Zset(有序集合)和Hash。这些数据类型都支持push,pop,add,remove,取交集,并集,差集等,这些操作都是原子性的,此外,Redis还支持一些排序和算法。

同时,redis会周期性的将更新后的数据写入磁盘,或者把修改操作写入追加的记录文件中(AOF和RDB两种方式),并且在此基础上实现了主从同步,就算机器重启后,也能通过持久化数据自动重建内存,使用Redis作为Cache,那么机器宕机之后热点数据也不会丢失。

可能存在的问题

  • 缓存和数据库的双写一致性
  • 缓存雪崩问题
  • 缓存击穿问题
  • 缓存的并发竞争问题

优点

  • 纯内存操作
  • 单线程操作,避免了频繁的上下文切换
  • 采用了非阻塞I/O多路复用机制

Redis中的数据结构

String

字符串,可以存放任意类型的数据,字符,整数,浮点数等。

一个字符串类型的值的容量有512MB,代表能存储最大512MB的内容。

List

列表,列表是Redis简单的字符串列表,按照插入顺序排序,可以通过LPUSHRPUSH命令添加一个元素到列表的头部或者尾部。

Set

和数据结构一样,在Redis中这是一个无序的,不允许相同的字符串合集。支持集合运算,如并集,交集,差集等。

Hash

散列,在redis中hash是字符串字段和字符串值之间的映射,主要用来表示对象,也能够用来表示对象,也能够存储许多元素。

Zset

有序集合,和之前的Set类似,是不包含相同字符串的合集,每个有序集合的成员都有关联着一个评分,这个评分用于把有序集合中的成员按最低分到最高分排序(如排行榜场景 TOP N)。

使用有序集合,可以非常方便的添加,删除和更新元素,元素是在插入时就做好排序处理的,所以可以很快地通过评分或者位次获得一个范围的元素。

SpringBoot中RedisTemplate

opsFor

在Spring中官方为我们封装了RedisTemplateStringRedisTemplate来对Redis进行操作。它们支持所有的原生Redis操作,在RedisTemplate中定义了5种对数据结构操作的方法。

  • opsForValue :操作字符串
  • opsForHash:操作散列
  • opsForList:操作列表
  • opsForSet:操作集合
  • opsForZSet:操作有序集合

我们通过这个opsFor()方法后可以对后面的操作进行链式调用,比如可以opsForValue().set(...)

Scan

RedisTemplate中操作有序集合,我们可以对其进行遍历操作,需要用到scan,就如下放所写

String [] strs = new String[]{"str1","str2"};
redisTemplate.opsForSet().add("set1",strs);
Cursor<String> cursor = redisTemplate.opsForSet().scan("set1", ScanOptions.NONE);
while (cursor.hasNext()) System.out.println(cursor.next());

我们可以理解为操作一个指针,对Set集合进行遍历操作。

ZSet

我们在RedisTemplate中操作有序集合时,需要设置值的score

ZSetOperations.TypedTuple<String> objectTypedTuple1 = new DefaultTypedTuple<>("zset5",9.6);

一如这般,之后我们使用Set集合来存放我们所有的值,再使用模板类中的add方法进行存入

 @Test
    void ZsetTest(){
        //redisTemplate.opsForZSet().removeRange("zsetTest",0,-1);
        ZSetOperations.TypedTuple<String> objectTypedTuple1 = new DefaultTypedTuple<>("zset5",9.6);
        ZSetOperations.TypedTuple<String> objectTypedTuple2 = new DefaultTypedTuple<>("zset2",9.9);
        ZSetOperations.TypedTuple<String> objectTypedTuple3 = new DefaultTypedTuple<>("zset3",10.5);
        ZSetOperations.TypedTuple<String> objectTypedTuple4 = new DefaultTypedTuple<>("zset1",99.0);
        Set<ZSetOperations.TypedTuple<String>> tuples = new HashSet<>();
        tuples.add(objectTypedTuple1);
        tuples.add(objectTypedTuple2);
        tuples.add(objectTypedTuple3);
        tuples.add(objectTypedTuple4);
        System.out.println(redisTemplate.opsForZSet().add("zsetTest",tuples));
        System.out.println(redisTemplate.opsForZSet().range("zsetTest",0,-1));
    }

我们可以看到控制台输出的结果

4
[zset5, zset2, zset3, zset1]

还有一种添加方法

@Test
    void ZsetAdd(){
        redisTemplate.opsForZSet().add("zsetTestAdd","zset1",55.0);
        redisTemplate.opsForZSet().add("zsetTestAdd","zset2",1.0);
        System.out.println(redisTemplate.opsForZSet().range("zsetTestAdd",0,-1));
        System.out.println(redisTemplate.opsForZSet().removeRange("zsetTestAdd",0,-1));
    }

RedisTemplate和StringRedisTemplate

两者的数据是不互通的,StringRedisTemplate只能管理StringRedisTemplate中的数据,同理亦然。

StringRedisTemplate默认采用的是String的序列化策略,RedisTemplate默认采用的是JDK的序列化策略。

SpringBoot配置Redis

配置Redis配置类(Config)

创建RedisConfig配置类

/**
 * @作者: Seale
 * @时间: 2020/09/07 14:55
 * @说明: Redis 配置类
 * @类名: RedisConfig
 */
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
    //在缓存对象集合中,缓存是以键值对的形式保存的
    //如果没有指定缓存的 key ,则Spring Boot会使用SimpleKeyGenerator生成key
    @Bean
    public  KeyGenerator keyGenerator(){
        return (o, method, objects) -> {
            //自定义缓存数据Key生成策略
            StringBuilder sb = new StringBuilder("imsle-");
            sb.append(o.getClass().getName())
                    .append("-")
                    .append(method.getName());
            for (Object obj : objects){
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory){
        return RedisCacheManager.create(factory);
    }
    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(factory);
        return stringRedisTemplate;
    }
}

那么之后就可以用Spring Boot提供的注解式缓存来进行缓存管理了。

]]>
3 /archives/redis.html#comments /feed/archives/redis.html
消息队列之RabbitMQ并在SpringBoot下简单集成 /archives/RabbitMQ1.html /archives/RabbitMQ1.html Sat, 22 Aug 2020 00:19:00 +0800 Seale 简介

RabbitMQ是开源的高级消息队列协议,它是用Erlang语言进行开发的,支持多种客户端。

RabbitMQ是目前应用广泛的消息中间件,当然目前的使用趋势逐步的偏向RocketMQKafka

在企业级应用,电商应用,微服务等等应用中,消息队列都担任着相当重要的角色,例如在业务服务模块中的解耦,异步通信,削峰,限流,超时业务等。

但值得注意的是RabbitMQ和单机吞吐量远不如RocketMQKafka,社区活跃度目前处于不冷不热的状态,由于它是基于Erlang开发,所以它的并发能力很强,性能极好,延时很低。

使用场景

推送通知

这里也就是我们常说的”发布\订阅“功能,它也是RabbitMQ中的重要功能。我们可以用”发布\订阅“功能来实现通知等。消费者(consumer)监听RabbitMQ队列中的数据。如果队列中有数据,则消费者会按照”先进先出“的原则逐条进行消费。而生产者(producer)只需要将数据存入队列中即可。这样降低了不同系统之间的耦合度,也确保了消息通知的及时性,且不影响系统的性能。

”发布\订阅“中支持三种模式:一对一、一对多、广播。着三种模式都可以根据规则选择分发的对象,同时,消费者(consumer)也可以制定规则来选择是否接受这些订阅数据。

异步任务

后端系统街道任务之后,将其分解成多个小任务,只要完成这些小任务,那么整个流程任务就可以完成。但是如果某一个或者某几个小任务是耗时任务,且对核心任务影响不大,则可以将这些任务放入消息队列中去处理,加快主要业务的处理速度。

跨应用通信

RabbitMQ中提供两种事务模式:

  • AMQP事务模式
  • Confirm事务模式

RabbitMQ可以用于不同开发语言应用之间的通信(如Java和C++进行通信),实现企业应用的集成。由于消息队列是和平台和语言无关的,并且语义上也不是函数调用,因此,RabbitMQ适合作为多个应用之间的中间件或者说是接口,且不需要发送方和接收方同时在线。

不同语言的解耦,可以最大幅度的减少此程序之间的相互依赖,提高系统的可用性以及可扩展性。

消息延迟

这个场景常出现在电商系统中,利用RabbitMQ消息队列延迟,可以实现订单、支付过期定时过期定时取消等功能。延迟队列存储延时消息,所以当消息被发送以后,消费者不是立即拿到消息,而是等待指定时间之后才能拿到这个消息进行消费。

当然,这个功能也可以利用其它第三方库或者定时任务进行开发。

但值得注意的是,单用RabbitMQ来实现的话,需要确认版本在3.5.8及以上,同时一般需要官方提供的依赖插件rabbitmq_delayed_message_excahnge来实现。

常见的实现途径是通过定时任务来完成的。

远程过程调用

在实际的应用场景中,有时候需要一些同步处理,以等待服务器端将消息处理完成后再执行下一步操作,这相当于RPC(远程过程调用)。RabbitMQ也支持RPC。

基本概念及特性

特性

  • 信息确认:自动应答和手动应答
  • 队列持久化
  • 信息持久化
  • 消息拒收
  • ...

生产者、消费者和代理

  • 生产者: 消息的创建方,负责创建和推送数据到消息服务器。
  • 消费者: 消息的接收方,用于处理数据和确认消息。
  • 代理: RabbitMQ本身,本身并不产生数据,扮演搬运工的角色。

消息队列

MQ的全称是Message Queue,意为消息队列之意。Queue是RabbitMQ的内部对象,用于存储生产者的消息知道发送给消费者进行消费,也是消费者接受消息的地方。RabbitMQ的消息也都之恶能存储在Queue中,多个消费者可以订阅同一个Queue。

Queue有以下一些重要的属性:

  • 持久性: 如果启用,则队列将会在消息协商器(broker)重启之前都有效。
  • 自动删除: 如果启用,则队列将会在所有消费者停止使用之后自动删除掉。
  • 惰性: 如果没有声明队列,则应用程序调用队列时导致的异常不会主动报警。
  • 排他性: 如果启用,则声明它的消费者才能使用。

交换机(Exchange)

交换机用于接收、分配消息。生产者需要预先指定一个routing key,然后将消息发送到交换机,这个routing key需要与Exchange Typebinding key联合使用才能最终生效,然后交换机将消息路由到一个或者多个队列中。

在虚拟主机的消息协商器(broker)中,每个Exchange都有唯一的名字。

Exchange包含4中类型:

  • direct
  • topic
  • fanout
  • headers

以上不同的类型代表着绑定的队列的行为的不同。

direct

此类型的行为是先匹配,再投送,在绑定队列时会设定一个routing key,只有在消息的routing key与队列匹配时,消息才会被交换机投送到绑定的额队列中。允许一个队列通过一个固定的routing key进行绑定。

此模式是RabbitMQ的默认交换机模式,也是最简单的模式,它是根据routing key进行全文匹配去寻找队列的。

topic

此类型是按规则转发消息。意为主题交换机(topic exchange),转发效益主要根据通配符。队列和交换机的绑定会定义一种路由模式,通配符就要在这种路由模式和路由键之间匹配后,交换机才会转发消息。

在这种交换机模式下,路由键必须包含一个*号,主要用于匹配路由键指定位置的一个单词。

topic模式还支持消息的routing key,用*或者#的模式进行绑定,*匹配一个单词,#匹配0个或者多个单词。

headers

它根据应用程序消息的特定属性进行匹配,可以在binding key中标记消息为可选或者是必选。在队列与交换机绑定时,会设定一组键值对规则,消息中也包括一组键值对(headers属性),当这些键值对中有一对或者全部匹配时,消息则会被投递到对应的队列中去。

fanout

消息广播的模式,即将消息广播到所有绑定到它的队列中,不考虑routing key所设定的值。此模式下routing key会被忽略掉。

通道

有些应用需要与AMQP代理建立多个连接,但是同时开启多个TCP连接会消耗过多的系统资源,并使得防火墙的配置变得更加困难,所以使用AMQP中的通道(channel)有时也会被称为频道,来处理多连接,可以理解成一个TCP连接的多个子连接。

一个特定的通道上的通信和其他通道上的通信是完全隔离的,因此,每个AMQP方法都需要携带一个通道号,这样客户端就可以指定方法连接的通道。

消息确认 (message acknowledgement)

当一个消息从消息队列中投递给消费者之后,消费者会通知消息代理,这个过程可以是自动的,也可以由处理消息的应用的开发者手动执行,当消息确认启动时,消息代理需要接受到来自消费者的确认回执才完全将消息从队列中删除,这个可以理解成TCP双向连接,当双方确认连接后,则完成一次消息投递。

如果消息无法被成功路由,或被返回给发送者并且被丢弃,或者是消息代理执行了延期操作,则消息会被放入到一个死信队列中,此时,消息的发送者可以选择某些参数来处理这些情况。

工作模式

  • 简单模式:生产者,一个消费者
  • 工作队列模式:一个生产者,多个消费者,每个消费者获取到的消息唯一。
  • 订阅模式:一个生产者发送的消息会被多个消费者获取。
  • 路由模式:发送消息到交换机,并且要指定路由key(routing key),消费者在将队列绑定到交换机时需要指定路由key。
  • topic模式:根据主题进行匹配,此时队列血药绑定在一个模式上,并且通过通配符来匹配。

AmqpTemplate

在Spring中也提供了AMQP协议的模板操作类,用于发送和接受消息,它定义发送和接受消息等操作,还提供了RabbitTemplate用于实现AmqpTemplate接口,而且还提供了错误抛出类AmqpExceptionRabbitTemplate支持消息的确认和返回。

send()

提供三个重载方法

  • void send(Message message) throws AmqpException
  • void send(String routingKey, Message message) throws AmqpException
  • void send(String exchange, String routingKey, Message message) throws AmqpException

covertAndSend()

AmqpTemplate模板类还提供了这个方法来发送消息。此方法相当于是简化的send(),可以自动处理消息的序列化。

@Test
public void send(){
    Message message = MessageBuilder.withBody("content".getBytes())
        .setContentType(...)
        .setMessageId(..)
        .setHeader(..)
        .build();
    amqpTemplate.send("test",message);
}

@Test
public void covertAndSend(){
    amqpTemplate.convertAndSend("test","content");
}

这就是这两者的区别,但是实现的效果是一致的。

接受消息

接受消息可以有两种方式

  • 一种为调用receive方法,如果该方法没有获得消息,则直接返回null,此方法不阻塞。
  • 异步接收,通过注册一个Listener来实现消息的接收,接收消息需要指定队列,或者是设置默认的队列。

Spring Boot 集成

首先下载,安装,启动,配置账号密码,这些就略过了。

我们在pom文件中添加starter依赖。

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

本文所使用的SpringBoot版本为2.3.3发布版,之后我们编写接收者和发送者来进行测试。

在目录下新建receiversender包。

之后在其下新建ReceiverAReceiverBQueueSender类。

package com.imsle.rbmqtest.receiver;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues = "test") //监听队列
public class ReceiverA {
     /**
      * @方法名: QueueReceiver
      * @说明:
      * @param queue
      * @return: void
      * @作者: Seale
      * @时间:  2020/08/25 12:51
      */
     @RabbitHandler
    public void QueueReceiver(String queue){
        System.out.println("消息接收者A:\t"+queue);
    }
}
/************************************************************************************/

package com.imsle.rbmqtest.receiver;

import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@RabbitListener(queues = "test")
public class ReceiverB {
    @RabbitHandler
     /**
      * @方法名: QueueReceiver
      * @说明:
      * @param queue
      * @return: void
      * @作者: Seale
      * @时间:  2020/08/25 12:51
      */
    public void QueueReceiver(String queue){
        System.out.println("消息接收者B:\t"+queue);
    }
}
/**********************************************************************************/
package com.imsle.rbmqtest.sender;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class QueueSender {
    @Autowired
    private AmqpTemplate rabbitTemplate;
    public void send(String context){
        System.out.println("发送者:\t"+context);
        //将消息发送到队列中
        rabbitTemplate.convertAndSend("test",context);
    }
}

并且新建config包,在其下创建RabbitConfig配置类

package com.imsle.rbmqtest.config;

import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitMQConfig {

    @Bean
    public Queue queue(){
        System.out.println("开启队列test");
        return new Queue("test");
    }
}

最后编写测试类

package com.imsle.rbmqtest.sender;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Date;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class QueueSenderTest {

    @Autowired
    QueueSender queueSender;
    @Test
    public void sendTest(){
        int time = 2 ;
        for (int i = 0 ; i < time ; i++){
            String msg = "发送信息\t第"+(i+1)+"次\t"+new Date();
            queueSender.send(msg);

        }
    }

}

如果报错,那么去检查你的配置文件是否填写正确,在这里我修改过我的rabbitMQ的用户。

spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: root
    password: 123456

成功运行后的结果你将会看到

发送者:    发送信息    第1次 Tue Aug 25 13:10:11 CST 2020
发送者:    发送信息    第2次 Tue Aug 25 13:10:11 CST 2020
消息接收者B: 发送信息    第2次 Tue Aug 25 13:10:11 CST 2020
消息接收者A: 发送信息    第1次 Tue Aug 25 13:10:11 CST 2020
]]>
0 /archives/RabbitMQ1.html#comments /feed/archives/RabbitMQ1.html
[重新温习]Java反射 /archives/88.html /archives/88.html Wed, 29 Apr 2020 23:22:00 +0800 Seale 简述

反射在Java是一个非常重要的概念,同时反射也是框架设计的灵魂,反射使用的条件是必须得到class字节码文件。

反射机制是存在运行状态中的,对于任意一个类,都可以知道这个类当中的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取信息以及动态调用对象的方法的功能称为Java语言的反射机制。

要解析一个类,首先我们就需要获得该类的字节码文件。

总之,反射就是把Java类中的各种成分映射一个个Java对象(也可以理解成反序列化)

流程图

简单验证

编写简单Student类

package 反射;
public class Student {
    private String name ;
    private String sex;
    private Integer age;
    private void print(){
        System.err.print("姓名:"+name+"\t"+"性别:"+sex+"\t"+"年龄:"+age+"\t");
    }
}

编写反射类

package 反射;

/**
 * @ author Seale
 * @ Description: 反射学习
 * @ QQ:1801157108
 * @ Date 2020/4/29 21:36
 */
public class Invoke {
    public static void main(String[] args) {
        //实例化对象

        Student student = new Student();
        Class stuClass = student.getClass(); //获取Class对象
        System.out.println("当前类"+stuClass.getName()); // 输出当前类的名称

        Class stuClass2 = Student.class;
        System.out.println("和第一个对象是否相同: "+ (stuClass == stuClass2)); // 判断和第一个Class对象是否相同

        try {
            Class stuClass3 = Class.forName("反射.Student");
            System.out.println("和第二个对象是否相同: "+ (stuClass3 == stuClass2)); //判断和第二个Class对象是否相同
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

注意

三种生成Class的方式,常用的是第三种,反射类可以以字符串方式进行传入,同时也可以通过常用的配置文件进行读取,同时也是编写框架时最为常用的方式。

通过反射获取构造方法并且使用

改进Student类

package 反射;

/**
 * @ author Seale
 * @ Description: 反射理解
 * @ QQ:1801157108
 * @ Date 2020/4/29 21:33
 */

public class Student {
    private String name ;
    private String sex;
    private Integer age;
    private void print(){
        System.err.print("姓名:"+name+"\t"+"性别:"+sex+"\t"+"年龄:"+age+"\t");
    }

    public Student() {
        //无参构造方法
        System.out.println("公共无参构造方法被调用......");
    }
    protected Student(Boolean isTrue){
        System.out.println("受保护的构造方法是否被调用:"+(isTrue?"是":"不是"));
    }
    public Student(boolean isTrue){
        System.out.println("公有的构造方法是否被调用:"+(isTrue?"是":"不是"));
    }
    public Student(String name, String sex, Integer age) {
        this.name = name;
        this.sex = sex;
        this.age = age;
        print();
    }

}

编写测试类

package 反射;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

/**
 * @ author Seale
 * @ Description:反射构造
 * @ QQ:1801157108
 * @ Date 2020/4/29 21:51
 */
public class ConstructorsTest {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Class clazz = Class.forName("反射.Student");

        System.out.println("------------所有公共构造------------");
        Constructor[] constructors = clazz.getConstructors();
        for (Constructor c : constructors) System.out.println(c);
        System.out.println("----------------------------------");

        //**********************************************************-
        System.out.println("------------所有构造(包括私有/默认/公有/受保护)------------");
        constructors = clazz.getDeclaredConstructors();
        for (Constructor c : constructors) System.out.println(c);
        System.out.println("------------------------------------------------------");

        //**********************************************************-
        System.out.println("------------获取参数是bool类型的公有构造------------");
        //获取私有等受保护的前面加上Declared即可
        Constructor con = clazz.getConstructor(boolean.class);
        System.out.println(con);
        //调用方法
        Object o = con.newInstance(true);
        System.out.println("---------------------------------------------");

    }

}
------------所有公共构造------------
public 反射.Student(boolean)
public 反射.Student(java.lang.String,java.lang.String,java.lang.Integer)
public 反射.Student()
----------------------------------
------------所有构造(包括私有/默认/公有/受保护)------------
public 反射.Student(boolean)
public 反射.Student(java.lang.String,java.lang.String,java.lang.Integer)
public 反射.Student()
protected 反射.Student(java.lang.Boolean)
------------------------------------------------------
------------获取参数是bool类型的公有构造------------
public 反射.Student(boolean)
公有的构造方法是否被调用:是
---------------------------------------------

获取成员变量

package 反射;

import java.lang.reflect.Field;

/**
 * @ author Seale
 * @ Description: 属性
 * @ QQ:1801157108
 * @ Date 2020/4/29 22:19
 */
public class Fields {
    public static void main(String[] args) throws Exception{
        //获取Class对象
        Class clazz = Class.forName("反射.Student");
        System.out.println("----------------所有的字段(包括受保护)---------------");
        Field[] fields = clazz.getDeclaredFields();
        for (Field f : fields)System.out.println(f);
        System.out.println("-------------------------------------------------");
        System.out.println();System.out.println();
        System.out.println("---------------------获取特定属性------------------");
        Field stu_name = clazz.getDeclaredField("name");
        System.out.println("当前获取的属性名称:\t"+stu_name);
        Field stu_sex = clazz.getDeclaredField("sex");
        //获取一个对象
        Object obj = clazz.getConstructor().newInstance();
        //属性设置
        stu_name.setAccessible(true); //暴力赋值 , 解除私有限制
        stu_sex.setAccessible(true);
        stu_name.set(obj,"反射给定的姓名");
        stu_sex.set(obj,"男");
        System.out.println("姓名:\t"+((Student) obj).getName());
        System.out.println("性别:\t"+((Student) obj).getSex());
        System.out.println("-------------------------------------------------");
    }
}
----------------所有的字段(包括受保护)---------------
private java.lang.String 反射.Student.name
private java.lang.String 反射.Student.sex
private java.lang.Integer 反射.Student.age
-------------------------------------------------

---------------------获取特定属性------------------
当前获取的属性名称:  private java.lang.String 反射.Student.name
公共无参构造方法被调用......
姓名: 反射给定的姓名
性别: 男
-------------------------------------------------

获得成员方法

修改Student

private void print1(){
        System.out.println("print1->私有");
    }
    protected void print2(){
        System.out.println("print2 -> 受保护");
    }
    public  void print3(){
        System.out.println("print3 -> 公有");
    }

编写测试

package 反射;

import java.lang.reflect.Method;

/**
 * @ author Seale
 * @ Description: 反射成员方法
 * @ QQ:1801157108
 * @ Date 2020/4/29 22:58
 */
public class MethodsClass {

    public static void main(String[] args) throws Exception{
        Class clazz = Class.forName("反射.Student");
        //获取公有方法
        System.out.println("-----------获取所有的公有方法---------");
        Method[] methods = clazz.getMethods();
        for (Method m : methods) System.out.println(m);
        System.out.println("-----------------------------------");

        System.err.println("--------------获取所有方法------------");
        methods = clazz.getDeclaredMethods();
        for (Method m : methods) System.err.println(m);
        System.err.println("------------------------------------");

        System.out.println("--------------获取特定方法------------");
        Method print1 = clazz.getDeclaredMethod("print1");
        System.out.println(print1);
        Object obj = clazz.getConstructor().newInstance();
        print1.setAccessible(true); //放开私有限制
        print1.invoke(obj); //调用方法
        System.out.println("------------------------------------");

    }

}
-----------获取所有的公有方法---------
public java.lang.String 反射.Student.getName()
public void 反射.Student.print3()
public java.lang.String 反射.Student.getSex()
public java.lang.Integer 反射.Student.getAge()
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public java.lang.String java.lang.Object.toString()
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
-----------------------------------
--------------获取特定方法------------
private void 反射.Student.print1()
公共无参构造方法被调用......
print1->私有
------------------------------------
--------------获取所有方法------------
public java.lang.String 反射.Student.getName()
private void 反射.Student.print()
public void 反射.Student.print3()
public java.lang.String 反射.Student.getSex()
public java.lang.Integer 反射.Student.getAge()
protected void 反射.Student.print2()
private void 反射.Student.print1()
------------------------------------

Process finished with exit code 0

获取Main并且执行

public class MainClasses {
    public static void main(String[] args) throws Exception {
        Class clazz = Class.forName("反射.Student");
        Method main_Method = clazz.getMethod("main", String[].class);
        main_Method.invoke(null, (Object) new String[]{});
    }
}
main被执行了!!!
]]>
0 /archives/88.html#comments /feed/archives/88.html
SpringBoot+JWT+SpringSecurity+MP实现RestFul接口的鉴权并实现Filter异常捕获 /archives/86.html /archives/86.html Sat, 08 Feb 2020 18:02:00 +0800 Seale SpringBoot+JWT+SpringSecurity+MP实现RestFul接口的鉴权

前言

写本篇文章的目的是为了帮助自己回忆复习也是为了帮助需要使用Java做后端接口的童鞋们, 方便自己也方便他人吧!

在我之前的一篇文章里面介绍的是使用JWT+SSM框架实现接口的鉴权, 在这里我将介绍一下SpringBoot+Security的鉴权方式

再指正一下, 这里的 MP 指的是, emmmm不是你们想得那样

Mybatis-plus 简称 MP

本篇文章着重介绍SpringSecurity的实现方式, MP等等自己找一下教程看看吧, 网上资料还是很多的

最后提一句, 本篇文章所写的代码仅适用于我的毕设项目, 不代表可以用于线上项目, 如果需要可能需要进一步优化, 蟹蟹!

什么是JWT

简介

JWT是Json Web Token的缩写简称, JWT是JSON格式的Web密钥

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。由于此信息是经过数字签名的,因此可以被验证和信任。可以使用秘密(使用HMAC算法)或使用RSA或ECDSA的公用/专用密钥对对JWT进行签名

尽管可以对JWT进行加密以在各方之间提供保密性,但我们将重点关注已签名的令牌。签名的令牌可以验证其中包含的声明的完整性,而加密的令牌则将这些声明隐藏在其他方的面前。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是对其进行签名的一方。

以上是机翻官网的文档

What is JSON Web Token?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA or ECDSA.
Although JWTs can be encrypted to also provide secrecy between parties, we will focus on signed tokens. Signed tokens can verify the integrity of the claims contained within it, while encrypted tokens hide those claims from other parties. When tokens are signed using public/private key pairs, the signature also certifies that only the party holding the private key is the one that signed it.

我们该什么时候使用它

这里先大概说一下两种场景:

[tabs]
[tab name="授权/鉴权" active="true"]

这个是JSON WEB令牌(JWT)中最常见的方案, 一旦用户登录, 每个后续请求都将包括JWT, 从而判断该用户是否有权限访问相对应的路由, 服务和资源

单点登录就是当下使用最广泛的JWT场景, 因为它对服务器的开销极小, 并且能够在不同中快捷的使用

[/tab]
[tab name="信息交换"]

JSON Web令牌是在各方之间安全地传输信息的好方法。因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确定发件人是他们所说的人

此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改

[/tab]
[/tabs]

JWT如何工作(How To Work?)

在身份验证鉴权中, 当用户进行成功登陆的时候, 服务器将返回一个JSON WEB 令牌(JWT), 这一串JWT格式的编码便是我们的凭据, 因此, 我们应该格外的小心, 并且进行一些安全保护措施, 通常, 权限等级越高的用户组别的令牌保留时间应该越短

关于JWT的一些基础详见我的另外一篇文章

[post cid="28" /]

由于缺乏安全性, 你也不应该将敏感的会话数据存储在浏览器中

每当用户想要访问受保护的路由或资源时,用户代理通常应使用承载模式在授权标头中发送JWT 。标头的内容应如下所示:

Authorization: Bearer <token>

在某些情况下,这可以是无状态授权机制。服务器的受保护路由将在Authorization标头中检查有效的JWT ,如果存在,则将允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库中某些操作的需求,尽管这种情况并非总是如此。

如果令牌是在Authorization标头中发送的,则跨域资源共享(CORS)不会成为问题,因为它不使用cookie。

当然, 如果你的项目已经配置了跨域, 你也可以将token放在参数中, 每次请求带上token的参数, 那么也能够成功进行鉴权操作

格式

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
header{
    "alg": "HS256",
    "typ": "JWT"
}
playload{
    "sub": "1234567890",
    "name": "John Doe",
    "iat": 1516239022
}
VERIFY SIGNATURE{
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    your-256-bit-secret
}

如有更多疑问, 请查看官方文档的介绍

[post url="https:\/\/jwt.io\/introduction\/" title="JWT. IO" intro="JWT官方网站" /]

SpringSecurity

这个是Spring提供的一套全面的安全框架, 在我的另一篇文章也有简单的介绍

[post cid="74" /]

SpringBoot实现

引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatisPlus.version}</version>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

这里需要注意的是,如果你想要使用最新版本的JJWT,请查看官方文档的介绍,则需要向如下引用JJWT


<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.0</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.0</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
     <!-- or jjwt-gson if Gson is preferred -->
     <!-- 在这里你也可以引入jjwt-gson -->
     <!-- 意思是你选择jjwt所进行的序列化库为Gson -->
    <version>0.11.0</version>
    <scope>runtime</scope>
</dependency>

一些准备

在常量池中加入常量

package com.eendtech.witkey.constants;

import lombok.Getter;

/**
 * @ author Seale
 * @ Description:  项目基本常量池
 * @ QQ:1801157108
 * @ Date 2020/2/1 19:22
 */
public class BaseConstant {

    @Getter
    public enum BaseConfig{
        DEFAULT_SIZE(10),
        DEFAULT_PAGE(1);

        private Integer val;
        BaseConfig (Integer val){
            this.val = val;
        }
    }

    @Getter
    public enum User {
        GROUP_TYPE_USER("用户组",0),
        GROUP_TYPE_ADMIN("管理组",1);
        private Integer val ;
        private String name ;
        User(String name , Integer val){
            this.name = name ;
            this.val = val;
        }
    }

    @Getter
    public enum Oauth{
        TOKEN_PREFIX("Bearer "),
        HEADER_PARAM("Authorization");
        private String val;
        Oauth(String val){
            this.val = val;
        }
    }

    @Getter
    public  enum Returns {
        SUCCESS(200, "成功"),
        TIMEOUT(201, "请求超时,或许是服务器原因"),
        FAIL(202, "请求失败"),
        PARAM_ERRO(203,"参数传输错误"),
        NOTAUTH(300, "无权限请求"),
        TOKEN_PARSE_FAILED(301,"Token校验失败"),
        TOKEN_Expired(302,"Token令牌过期"),
        UNKNOWN_ERROER(800,"未知错误");

        private Integer code;
        private String status;

        private Returns(Integer code, String status) {
            this.code = code;
            this.status = status;
        }
    }

    @Getter
    public enum ReturnMessage{
        PARAM_NOT_NULL("必要参数不能为空"),
        USER_OR_PWD_ERR("帐号或密码错误,登录失败"),
        NOT_FOUND_TOKEN("没有携带Token,或者是Token校验失败"),
        NOT_OAUTH_REQUEST("没有权限访问");

        private  String val ;

        ReturnMessage (String val){
            this.val = val;
        }
    }
    .....

}

以上是本篇文章所需要的常量

新增用户注册的控制器

package com.eendtech.witkey.controller;

import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.model.User;
import com.eendtech.witkey.service.UserService;
import com.eendtech.witkey.utils.BaseUtils;
import com.eendtech.witkey.utils.ResultUtils;
import com.eendtech.witkey.utils.ReturnsKit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

/**
 * @ author Seale
 * @ Description:
 * @ QQ:1801157108
 * @ Date 2019/11/30 21:49
 */
@RestController
@RequestMapping("/api/oauth")
public class LoginController {
    @Autowired
    UserService userService;
    @Autowired
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @PostMapping("/register")
    public ReturnsKit register(@RequestBody User user) {
        if (BaseUtils.verifyParam(user.getUsername(), user.getPassword())) {
            user.setPassword(bCryptPasswordEncoder.encode(user.getPassword()));
            String msg = userService.addUser(user);
            if (msg.equals("用户创建成功"))
                return ResultUtils.success(msg);
            else
                return ResultUtils.erro(BaseConstant.Returns.FAIL, msg);
        } else
            return ResultUtils.erro(BaseConstant.Returns.PARAM_ERRO, BaseConstant.ReturnMessage.PARAM_NOT_NULL);
    }

    @GetMapping("/check/{username}")
    public ReturnsKit check(@PathVariable String username) {
        if (BaseUtils.verifyParam(username)) {
            String msg = userService.chekUserName(username);
            return ResultUtils.success(msg);

        }else return ResultUtils.erro(BaseConstant.Returns.PARAM_ERRO, BaseConstant.ReturnMessage.PARAM_NOT_NULL);
    }

}

这里需要注意的是,SpringSecurity中默认要求是需要进行密码加密的,如果你的密码不进行加密会报异常

JWT工具类的封装

上一篇SSM框架集成JWT鉴权的文章,对JWT工具类的封装并不是很好,归结一点,当时比现在还要菜一点

package com.eendtech.witkey.utils;

import com.eendtech.witkey.Exception.TokenException;
import com.eendtech.witkey.constants.BaseConstant;
import io.jsonwebtoken.*;
import io.jsonwebtoken.impl.Base64UrlCodec;
import io.jsonwebtoken.impl.DefaultClaims;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;

/**
 * @ author Seale
 * @ Description: JJWT签发工具类
 * @ QQ:1801157108
 * @ Date 2020/2/7 19:55
 */
public class JwtUtils {

    private static final String SECRET = "EENDTECHHCETDNEE520131....4!@#$%Y^ASDHUIsadasd#@#!@#156489d1svxcv4cx123a894!!@@@@###@{}{}{gasyudgas}JAIOASJD";
    private static final String ISS = "EENDTECH";//签发者

    //默认过期时间是1天
    private static final long EXPIRATION = 3600 * 24 * 1000L;

    //生成TOKEN

    /***
     * 函数名: GenToken
     * 函数说明: 生成TOKEN令牌
     * 创建时间: 2020/2/7 20:01
     * @Param userName: 用户名
     * @Param group: 当前用户组
     * @Param isForever: 是否为永久令牌
     * @param uid: 用户的id
     * @return: java.lang.String
     */
    public static String GenToken(String userName, Integer uid, String group, boolean isForever) {
        Claims claims = new DefaultClaims();
        claims.setId(uid.toString())
                .setIssuer(ISS) //签发者
                .setIssuedAt(BaseUtils.getTimeNow()) // 签发时间
                .setAudience(userName) //接收方
                .put("group", group);
        claims.put("user", userName);
        JwtBuilder jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, generalKey())
                .setId(uid.toString())
                .setClaims(claims);
        System.err.print("当前生成的signKey:" + "\t");
        for (byte i : generalKey().getEncoded()) System.err.print(i);
        if (!isForever) {
            long exp = System.currentTimeMillis() + EXPIRATION;
            jwt.setExpiration(new Date(exp)); // 设置过期时间为一天后
        }
        String r = jwt.compact();
        System.out.println("\n原始Token\t" + r);
        // 对结果进行Base64Url编码后发送
        r = Base64UrlCodec.BASE64URL.encode(r);

        return r; //返回生成的令牌
    }

    //从令牌中获取用户名
    public static String getUserName(String token) {
        return validateJwt(token).get("user").toString();
    }

    //从令牌中获取uid
    public static String getUid(String token) {
        return validateJwt(token).getId();
    }

    //从令牌中获取用户组
    public static String getGroup(String token) {
        return validateJwt(token).get("group").toString();
    }

    /***
     * 函数名: validateJwt
     * 函数说明: 验证当前的令牌
     * 创建时间: 2020/2/7 20:56
     * @Param token:
     * @return: com.eendtech.witkey.utils.ReturnsKit
     */
    public static Claims validateJwt(String token) {
        Claims claims = null;
        try {
            claims = parseToken(token);
        }catch (ExpiredJwtException e) {
            System.err.println("当前Token过期了!");
        }
        catch (SignatureException e1) {
            System.err.println("当前Token检验失败,不符合加密规则");
            throw new TokenException(BaseConstant.Returns.TOKEN_PARSE_FAILED,"当前Token检验失败,不符合加密规则");
        }
        if (claims == null){
            throw new TokenException(BaseConstant.Returns.TOKEN_PARSE_FAILED,"token校验失败");
        }
        return claims;
    }

    // 解析Token playload
    public static Claims parseToken(String token) {
        //对token进行解码
        token = Base64UrlCodec.BASE64URL.decodeToString(token);
        return Jwts.parser()
                .setSigningKey(generalKey())
                .parseClaimsJws(token)
                .getBody();

    }

    public static SecretKey generalKey() {
        byte[] encodeDkey = SECRET.getBytes();
        return new SecretKeySpec(encodeDkey, 0, SECRET.length(), "AES");
    }
}

上面留有一些测试打印代码,我就没删除了!

在这里你会发现我们对JWT的Token进行验证的时候捕获了JWT库中抛出的一些基本异常,但是为什么我又抛出了一个TokenException异常呢?

这一点容我后面详谈

我们先介绍一下这个工具类的大体作用,首先,是这个核心方法GenToken,他的作用是生成一个token令牌,在这个工具类中我们对一些必要参数进行了初始化,比如SECRETISS -> 密钥和签发者,同时也设置了默认的过期时间为1天,当然,这些常量你也可以将它们移动到常量池中

我们通过Jwts.builder().**.compact()来构建一个String类型的Token令牌

但是,指的注意的是,在这里生成的Token令牌的格式为标准的JWT格式

也就是说我们得到的String字符串为

abcd.efgasdasd.asdasd

你或许会有疑问了,这有什么不对吗?确实,如果说你将Token放在Header中来进行鉴权,那么确实没有问题,但是有些场景下,我们需要将这个token放在我们的URL后面,也就如下所示

https://imsle.com/test?id=xx&page=1&token=

显然我们不可能那么做,所以在这里我们需要进行一次Base64URL的转码操作,当我们从request中读取到token的时候又需要进行对token的Base64URL的解码操作

        //对token进行解码
        token = Base64UrlCodec.BASE64URL.decodeToString(token);

        // 对结果进行Base64Url编码后发送
        String r = jwt.compact();
        r = Base64UrlCodec.BASE64URL.encode(r);

这样一来,我们得到的token就类似于这样了

ZXlKaGJHY2lPaUpJVXpVeE1pSjkuZXlKcWRHa2lPaUkzSWl3aWFYTnpJam9pUlVWT1JGUkZRMGdpTENKcFlYUWlPakUxT0RFeE5EWTBOVFVzSW1GMVpDSTZJblJsYzNReE1URXhJaXdpWjNKdmRYQWlPaUpTVDB4RlgxVlRSVklpTENKMWMyVnlJam9pZEdWemRERXhNVEVpZlEud2FKaGlaUE1PZG9lWXA4MGk2aWFOTTVERjlGQVY5Zzh6V2sxUnBmOHZpdzRmWWlacG10RWE2RzgtLVVQNlNmTWUtRmFySm91Sjk4MFFYRFpBOVRtSGc

其中的.已经被转换掉了,这个时候我们就可以放心的在url后接token参数了

封装其他基本工具类

返回工具类

package com.eendtech.witkey.utils;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * @ author Seale
 * @ Description: Json返回包集合类
 * @ QQ:1801157108
 * @ Date 2020/2/1 19:18
 */
@Getter@Setter
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ReturnsKit <T>{
    //状态码
    private Integer code;
    //结果
    private T result;
    //消息
    private String message;
    //状态
    private String status;
}

结果返回工具类

package com.eendtech.witkey.utils;

import com.eendtech.witkey.constants.BaseConstant;

import javax.servlet.http.HttpServletResponse;

/**
 * @ author Seale
 * @ Description: 结果返回工具类
 * @ QQ:1801157108
 * @ Date 2020/2/6 12:23
 */

public class ResultUtils {
    @SuppressWarnings("unchecked")
    public static <T> ReturnsKit<T> success(Object result) {
        ReturnsKit returnsKit = new ReturnsKit();
        return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                .setResult(result);
    }

    public static ReturnsKit<String> success(String msg) {
        return new ReturnsKit<String>()
                .setCode(BaseConstant.Returns.SUCCESS.getCode())
                .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                .setMessage(msg);
    }
    public static ReturnsKit<String> erro(BaseConstant.Returns returns,String msg){
        return new ReturnsKit<String>().setCode(returns.getCode())
                .setStatus(returns.getStatus())
                .setMessage(msg);
    }
    public static ReturnsKit<String> erro(BaseConstant.Returns returns,BaseConstant.ReturnMessage message){
        return new ReturnsKit<String>().setCode(returns.getCode())
                .setStatus(returns.getStatus())
                .setMessage(message.getVal());
    }
    public static void initResponse(HttpServletResponse response){
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
    }
}

实现UserDetailService

package com.eendtech.witkey.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.eendtech.witkey.mapper.UserMapper;
import com.eendtech.witkey.model.JwtUser;
import com.eendtech.witkey.model.User;
import com.eendtech.witkey.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * @ author Seale
 * @ Description:
 * @ QQ:1801157108
 * @ Date 2020/2/7 19:48
 */
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserService service;
    @Autowired
    private UserMapper mapper;
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(User::getUsername,s);
        User user = service.getOne(wrapper);
        String groupName = mapper.getUserGroup(user.getId());
        return new JwtUser(user, groupName);
    }
}

我们知道,SpringSecurity框架自身需要调用这个服务类来查询我们的用户的权限相关信息,所以我们要实现它来供框架调用

其中的JWTUser是我对User基本对象的进一步包装,同时这个需要对UserDetails进行实现

package com.eendtech.witkey.model;

import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Collections;

/**
 * @ author Seale
 * @ Description:
 * @ QQ:1801157108
 * @ Date 2020/2/7 21:10
 */
@Getter
@ToString
public class JwtUser implements UserDetails {

    private Integer id ;
    private String username;
    private String password;
    private Integer groupId;
    private String groupName;
    //鉴权用的,该用户具有哪些权限,目前我暂时存放我的groupName
    private Collection<? extends GrantedAuthority> authorities;

    public JwtUser(User user,String groupName) {
        this.id = user.getId();
        this.username = user.getUsername();
        this.password = user.getPassword();
        this.groupId = user.getGroupId();
        this.groupName = groupName;
        this.authorities = Collections.singleton(new SimpleGrantedAuthority(groupName));
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

    //帐号是否未过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    //帐号是否未锁定
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    //帐号凭证是否未过期
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    // 是否开启
    @Override
    public boolean isEnabled() {
        return true;
    }

}

对应表的sql脚本

CREATE TABLE `user` (
  `id` int(255) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '密码\r\n',
  `groupId` int(2) unsigned NOT NULL DEFAULT '0' COMMENT '0 -> 普通用户 1-> 管理组',
  `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮件地址',
  `createTime` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `onlyUser` (`username`) COMMENT '用户名唯一',
  KEY `group` (`groupId`),
  CONSTRAINT `用户组` FOREIGN KEY (`groupId`) REFERENCES `groups` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `groups` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(255) NOT NULL COMMENT '用户组名',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

UserDetailsServiceImpl中我们进行了groupName查询,并且将它放进JWTUser以便之后进行get()

实现两大过滤器(核心)

JWTAuthenticationFilter

其实,在SpringSecurity中最核心的部分也就是两大过滤器的编写了

我们首先完成 JWTAuthenticationFilter,也就是我们平时所说的获取token接口/登录接口

我们通过Filter实现token的生成并判断用户的凭据是否正确,也就是框架本身调用的UserDetailsService这个类,与之相关联的也就是我们实现的UserDetailsJwtUser

package com.eendtech.witkey.Filter;

import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.model.JwtUser;
import com.eendtech.witkey.model.User;
import com.eendtech.witkey.utils.JwtUtils;
import com.eendtech.witkey.utils.ResultUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

/**
 * @ author Seale
 * @ Description: JJWT过滤器
 * @ QQ:1801157108
 * @ Date 2020/2/7 18:43
 */

/** 验证用户名密码正确之后生成一个token并且返回给客户端
 *  我们需要重写attemptAuthentication successfulAuthentication
 */
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    private AuthenticationManager authenticationManager ;

    public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
        this.authenticationManager = authenticationManager;
        super.setFilterProcessesUrl("/api/oauth/login");
    }

    // 接受并解析用户的凭证
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request,
                                                HttpServletResponse response) throws AuthenticationException {
        try {
            User user = new ObjectMapper().readValue(request.getInputStream(),User.class);
            System.err.println(user.getUsername()+"\t"+user.getPassword());
            return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>())
                    //这里的ArrayList是权限列表,本项目在里面只存取了用户组信息
            );
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    //成功登录后 该方法被调用
    @Override
    protected void successfulAuthentication(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain chain,
                                            Authentication authResult) throws IOException, ServletException {
        JwtUser jwtUser = (JwtUser) authResult.getPrincipal();
        System.err.println("成功登录: -> \n"+"\t"+jwtUser);
        //生成一个永久令牌
        Collection<? extends GrantedAuthority> authorities = jwtUser.getAuthorities();
        String group = "";
        for (GrantedAuthority authority : authorities){
            group = authority.getAuthority();
        }
        String token = JwtUtils.GenToken(jwtUser.getUsername(),jwtUser.getId(),group,true);
        System.err.println(token);
        response.setHeader(BaseConstant.Oauth.HEADER_PARAM.getVal(), BaseConstant.Oauth.TOKEN_PREFIX.getVal()+token);
        ResultUtils.initResponse(response);
        Map<String,String> r = new HashMap<>();
        r.put("token",token);
        response.getWriter().write(new ObjectMapper().writeValueAsString(ResultUtils.success(r)));
    }
    // 这是验证失败时候调用的方法
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
        response.getWriter().write(new ObjectMapper().writeValueAsString(ResultUtils.erro(BaseConstant.Returns.FAIL, BaseConstant.ReturnMessage.USER_OR_PWD_ERR)));
    }
}

我在这里简单讲解以下,attemptAuthentication这个是我们进行登录鉴权用的接口,首先查看代码,我在里面通过JacksonObjectMapper对JSON进行序列化,我们拿到了usernamepassword

之后我们将它们提交给框架进行处理,也就是

return authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(user.getUsername(),user.getPassword(),new ArrayList<>())

这一句,因为此时权限列表是空的(还没有登录成功),所以我们生成一个空的列表或者填写null

successfulAuthentication这个接口是登录成功后调用,上面我们通过框架将我们的用户信息提交给了我们之前实现的服务类,进行了查询,所以我们在这里取得我们的JwtUser即可,由于框架返回的是Object类型,所以我们在这里需要进行一下强转

之后我们便通过我们封装的工具类生成一个永久token(这个根据自己的需求来进行更改,由于这个只是我的毕设项目,所以..),如果你想进行有效的管理token可以尝试进行集合redis,在这里我就不多说明了

生成成功之后我们通过返回工具进行内容的返回,官方建议的是返回到Header中,在上面的DEMO中我实现了两种方式,一种通过responseBody返回内容,一种加入到responseHeader中

JWTAuthorizationFilter

这个就是我们的鉴权接口了,我们拿到Token之后请求我们所需要的接口,便会调用这个Filter

package com.eendtech.witkey.Filter;

import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.utils.BaseUtils;
import com.eendtech.witkey.utils.JwtUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collections;

/**
 * @ author Seale
 * @ Description: Token的校验
 * @ QQ:1801157108
 * @ Date 2020/2/7 22:08
 */
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
    public JWTAuthorizationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);

    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException{
        String header = request.getHeader(BaseConstant.Oauth.HEADER_PARAM.getVal());
        if (header == null || !header.startsWith(BaseConstant.Oauth.TOKEN_PREFIX.getVal())) {
            chain.doFilter(request, response);
            return;
            //请求头没有token则直接放行
        }
        //进行解析
        //先对header进行验证

        /*try {
            JwtUtils.validateJwt(header.replace(BaseConstant.Oauth.TOKEN_PREFIX.getVal(), ""));
        }catch (IllegalArgumentException e){
            if (e.getMessage().equals("JWT String argument cannot be null or empty.")){
                ResultUtils.initResponse(response);
                response.getWriter().write(new ObjectMapper().writeValueAsString(ResultUtils.erro(BaseConstant.Returns.TOKEN_PARSE_FAILED,"Token解析器解析失败,请携带正确的token")));
                return;
            }
        }*/

        SecurityContextHolder.getContext().setAuthentication(getAuthentication(header));

        super.doFilterInternal(request, response, chain);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
        String token = tokenHeader.replace(BaseConstant.Oauth.TOKEN_PREFIX.getVal(), "");

        String username = JwtUtils.getUserName(token);
        String group = JwtUtils.getGroup(token);

        if (BaseUtils.verifyParam(username)) return new
                UsernamePasswordAuthenticationToken(username, null, Collections.singleton(new SimpleGrantedAuthority(group)));
        return null;
    }
}

这个没有什么难度,我们先验证header是否为空,当然这里你也可以改成获取token参数

如果请求头没有token我们直接放行,框架会自行拦截

我们通过SecurityContextHolder.getContext().setAuthentication(getAuthentication(header));来将我们所有的权限加入到Security域中,这样框架本身就会调用UsernamePasswordAuthenticationToken(username, null, Collections.singleton(new SimpleGrantedAuthority(group))中的权限列表来识别我们的身份了

securityConfig

然后就是对我们的security进行配置了如下


package com.eendtech.witkey.config;

import com.eendtech.witkey.Filter.JWTAuthenticationFilter;
import com.eendtech.witkey.Filter.JWTAuthorizationFilter;
import com.eendtech.witkey.advice.JWTAccessDeniedHandler;
import com.eendtech.witkey.advice.JWTAuthenticationEntryPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

/**
 * @ author Seale
 * @ Description: Spring Security Configureation
 * @ QQ:1801157108
 * @ Date 2019/11/29 22:26
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    @Qualifier(value = "userDetailsServiceImpl")
    private UserDetailsService userDetailsService;

    @Autowired
    JWTAccessDeniedHandler jwtAccessDeniedHandler;

    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //对请求进行验证
        http.cors().and().csrf().disable()
                .authorizeRequests()
                // 对本接口的所有请求进行放行
                .antMatchers(HttpMethod.POST,"/api/order/**").authenticated()
                .antMatchers(HttpMethod.POST,"/api/message/**").authenticated()
                .antMatchers("/api/message/sendSys").hasAnyAuthority("ROLE_ADMIN")
                .antMatchers("/api/message/updateSysMsg").hasAnyAuthority("ROLE_ADMIN")
                .antMatchers("/api/message/getAllSysForGroup").hasAnyAuthority("ROLE_ADMIN")
                .antMatchers("/api/message/sendGroupSysMsg").hasAnyAuthority("ROLE_ADMIN")
                .antMatchers("/api/message/sendNotice").hasAnyAuthority("ROLE_ADMIN")
                .anyRequest().permitAll()
                .and()
                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)//不需要session
                .and()
                .exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
                .accessDeniedHandler(jwtAccessDeniedHandler)

        ;

    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
        return source;
    }

}

在这里进行配置哪些需要验证,哪些需要特定的权限

值得注意的是

                .addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))

这两个过滤器的顺序不能改变

进行异常的捕捉

完成上面的步骤就已经基本完成了对RestFul接口的鉴权了,但是如果我们携带错误或者没有携带Token进行请求接口的时候,那么后台返回的数据则是默认的异常处理页,这显然在业务开发中是不能忽视的

所以我们需要自己定义一些Handler来进行处理

package com.eendtech.witkey.advice;

import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.utils.ResultUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ author Seale
 * @ Description: 认证接口处理 -> 没有携带Token,Token校验失败
 * @ QQ:1801157108
 * @ Date 2020/2/8 10:18
 */
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest,
                         HttpServletResponse httpServletResponse,
                         AuthenticationException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");

        httpServletResponse.getWriter().write(new ObjectMapper()
                .writeValueAsString(ResultUtils.erro(BaseConstant.Returns.NOTAUTH, BaseConstant.ReturnMessage.NOT_FOUND_TOKEN)));
    }
}
package com.eendtech.witkey.advice;

import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.utils.ResultUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @ author Seale
 * @ Description: 没有访问权限
 * @ QQ:1801157108
 * @ Date 2020/2/8 10:31
 */
@Component
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        httpServletResponse.setCharacterEncoding("UTF-8");
        httpServletResponse.setContentType("application/json; charset=utf-8");
        httpServletResponse.getWriter().write(new ObjectMapper()
                .writeValueAsString(ResultUtils.erro(BaseConstant.Returns.NOTAUTH, BaseConstant.ReturnMessage.NOT_OAUTH_REQUEST)));
    }
}

以上是Security框架自带的异常处理,之后我们通过Controller AOP编程来进行增强操作,在SpringBoot中已经为我们实现了Controller的Advice

我们可以通过注解直接使用@RestControllerAdvice

由于这里我们是restful接口所以使用的上面的注解

package com.eendtech.witkey.advice;

import com.eendtech.witkey.Exception.TokenException;
import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.utils.ResultUtils;
import com.eendtech.witkey.utils.ReturnsKit;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @ author Seale
 * @ Description: Controller处理增强
 * @ QQ:1801157108
 * @ Date 2020/2/6 13:10
 */
@RestControllerAdvice
public class ExceptionControllerAdvice {

    @ExceptionHandler(value = Exception.class)
    public ReturnsKit<String> SimpleExceptionHandler(Exception e){
        return ResultUtils.erro(BaseConstant.Returns.UNKNOWN_ERROER,
                e.getMessage() == null? e.getClass().getName() : e.getMessage());
    }

    @ExceptionHandler(value = TokenException.class)
    public ReturnsKit<String> tokenParseFail(TokenException e){
        return ResultUtils.erro(e.getReturns(),e.getMsg());
    }
}

到这里,也就是我上文留下的坑了,这里我们定义了一个TokenException异常类

下面我们来看看这个异常类

package com.eendtech.witkey.Exception;

import com.eendtech.witkey.constants.BaseConstant;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * @ author Seale
 * @ Description:
 * @ QQ:1801157108
 * @ Date 2020/2/8 10:58
 */
@Getter
@Setter
@Accessors(chain = true)
public class TokenException extends RuntimeException {
    private static final long serialVersionUID = -739183828263221695L;
    private BaseConstant.Returns returns ;
    private String msg ;
    public TokenException(BaseConstant.Returns returns,String msg){
        super(returns.getStatus());
        this.returns = returns;
        this.msg = msg;
    }

}

启动项目后,你会发现,如果我输入错误的Token还是没有进行异常的捕获,这是为什么呢?

因为在Spring中Filter是最外层,执行顺序如下

Filter > ControllerAdvice > Controller

如此可见,就算我们定义了捕获异常的增强Controller但是依旧不能捕获Filter所抛出的异常

这里提出一个我的解决方案

我们可以重写Spring默认的基本异常捕获,在这里面再抛出我们自己所定义的异常,这样做的弊端,就是我们需要将可发现的异常全部定义,并且规范化的返回,否则或许会返回空文本在异常捕获数据中

package com.eendtech.witkey.controller;

import com.eendtech.witkey.Exception.TokenException;
import com.eendtech.witkey.constants.BaseConstant;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

/**
 * @ author Seale
 * @ Description: 全局异常转发抛出
 * @ QQ:1801157108
 * @ Date 2020/2/8 14:30
 */
@RestController
public class ErrorController extends BasicErrorController {
    public ErrorController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }
    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
        Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        //自定义的错误信息类
        //status.value():错误代码,
        //body.get("message").toString()错误信息
        if (!body.get("message").toString().isEmpty()) {
            //在这里可以进行更为详细的定制,当前项目的需求只是捕获Filter中的token解析异常
            String msg = body.get("message").toString();
            throw new TokenException(BaseConstant.Returns.TOKEN_PARSE_FAILED,msg);
        }
        return null;
    }

}

流程相当于这样:

首先我们的Filter抛出了异常 -> 进入到了Spring默认的ERRO捕获Controller -> 由于我们的重写进行抛出自定义的Exception -> 我们所定义的ControllerAdvice增强进行异常的捕获

测试

参考文献

JWT官网

Protect REST APIs with Spring Security and JWT

REST Security with JWT using Java and Spring Security

Spring Security Reference Doc

]]>
2 /archives/86.html#comments /feed/archives/86.html
Spring Boot 站内信的设计与实现 [已更新] /archives/76.html /archives/76.html Tue, 04 Feb 2020 17:32:00 +0800 Seale Spring Boot 站内信的设计与实现

什么是站内信

在WEB系统中,站内信相当于是一个托管在服务器上的轻量邮局,它可以从WEB系统或者是用户获取信息并发送给指定的用户或者是用户群体。

站内信的类型

据我所知,当前实现站内信的方式主要通过两种:

  • 通过Websocket来进行实现

  • 通过纯数据库来进行实现

同时,基础的站内信一般都会实现两大功能,点到点的消息传送,点到面的消息传送

点到点的消息传送

点到点的消息传送,类似于我们关系型数据库所讲的一对一关系,也就是说,用户可以对用户进行消息的投递,或者是说用户对管理员进行消息的投递

点到面的消息传送

点到面的消息传送,类似于关系型数据库所说的一对多关系,web后台对应多个用户,也就是说,web后台可以对多个用户(用户群)进行群发消息通知

站内信的应用场景

  • 通知公告

  • 营销类活动

  • 相关的信息通知

  • 用户之间的互动

  • 订阅信息
    ......

站内信的设计

在这里我们暂且只介绍下数据库的后端实现步骤,下次有机会的话介绍websocket方式

前端方面就靠你们自己吧

数据库

我们通过两张表实现,新建inner_messageinner_message_text

CREATE TABLE `inner_message_text` (
  `id` varchar(255) NOT NULL COMMENT '站内信内容id',
  `sendId` int(255) unsigned DEFAULT NULL COMMENT '发信者id',
  `content` text NOT NULL COMMENT '信件内容',
  `groupId` int(2) unsigned DEFAULT NULL COMMENT '群发站内消息用 0->群发用户组 1->群发管理组',
  `type` int(1) NOT NULL COMMENT '0 -> 系统消息 , 1 -> 私信 , 2 -> 站内通知',
  `postDate` datetime NOT NULL COMMENT '发信时间',
  PRIMARY KEY (`id`),
  KEY `suid` (`sendId`),
  KEY `groupId` (`groupId`),
  CONSTRAINT `groupId` FOREIGN KEY (`groupId`) REFERENCES `user` (`groupId`),
  CONSTRAINT `suid` FOREIGN KEY (`sendId`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `inner_message` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `getUid` int(255) unsigned NOT NULL COMMENT '接受者的id',
  `messageId` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '站内信内容id',
  `status` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '站内信的状态 -> 未读 已读 删除',
  PRIMARY KEY (`id`),
  KEY `mid` (`messageId`),
  KEY `uid` (`getUid`),
  CONSTRAINT `mid` FOREIGN KEY (`messageId`) REFERENCES `inner_message_text` (`id`),
  CONSTRAINT `uid` FOREIGN KEY (`getUid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=18 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

如上,我们分了两个表,一个存放信息列表,一个存放信息内容表,这样的好处就是解耦合,试想一下,倘若我们将所有数据放在一张表上,那么每个用户收到的信息都会被存入数据库中,几百人的使用量还好说,一旦用户数量上升,那么你的数据库只有一个结果:boom~

SpringBoot中要做的一些事情

持久化框架在这里我使用的是MybatisPlus,在这里我推荐一下jpabeetlSQL,用着是真滴舒服

这一些相应的配置我就不赘述了

我们在Constant池中进行常量的定义

    ...
    @Getter
    public enum Message{
        STATUS_NOTREAD("未读"),
        STATUS_READ("已读"),
        STATUS_DELETED("已删除"),
        TYPE_SYSTEM("系统消息",0),
        TYPE_PRIVATE("私信",1),
        TYPE_NOTICE("站内通知",2)
        GROUP_TYPE_USER("用户组",0),
        GROUP_TYPE_ADMIN("管理组",1)
        ;

        private Integer val;
        private String name;
        Message(String name , Integer val){
            this.val = val ;
            this.name = name ;
        }
        Message(String name){
            this.name = name ;
        }
    }
    ...

同时,我们在VO层中添加我们的MessageVO用于接收传递的数据对象


package com.eendtech.witkey.vo;

import lombok.Data;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * @ author Seale
 * @ Description: 站内信VO对象
 * @ QQ:1801157108
 * @ Date 2020/2/3 18:51
 */
@Data
@Accessors(chain = true)
public class MessageVO implements Serializable {

    private static final long serialVersionUID = -5424113637283045181L;

    private String messageId ;
    private Integer sendUid ;
    private Integer getUid ;
    private Integer type ;
    private Date postDate ;
    private Integer groupId;
    private String content ;
    private String status ;

}

以下是自定义的Mapper文件,当前使用的持久化框架是MybatisPlus简称MP

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.eendtech.witkey.mapper.MessageMapper">
    <!--根据站内信的接收人id和站内信的类型进行查询-->
    <resultMap id="findMessageByGetUidAndTypeResult" type="com.eendtech.witkey.model.Message">
        <id property="id" column="id"/>
        <result property="getUid" column="getUid"/>
        <result property="status" column="status"/>
        <result property="messageId" column="messageId"/>
        <association property="messageText" javaType="MessageText">
            <id property="id" column="mid"/>
            <result property="sendId" column="sendId"/>
            <result property="content" column="content"/>
            <result property="type" column="type"/>
            <result property="group" column="group"/>
            <result property="postDate" column="postDate"/>
        </association>
    </resultMap>

    <resultMap id="findSendMessageBySendUidMap" type="com.eendtech.witkey.model.Message">
        <id property="id" column="id"/>
        <result property="getUid" column="getUid"/>
        <result property="status" column="status"/>
        <result property="messageId" column="messageId"/>
        <association property="messageText" javaType="MessageText">
            <id property="id" column="mid"/>
            <result property="sendId" column="sendId"/>
            <result property="content" column="content"/>
            <result property="groupId" column="groupId"/>
            <result property="type" column="type"/>
            <result property="postDate" column="postDate"/>
        </association>
    </resultMap>

    <sql id="selectContent">
        m.id, m.getUid, m.messageId, m.status, t.id as mid, t.sendId, t.content, t.type, t.postDate ,t.groupId
    </sql>
    <sql id="systemContent">
        id,sendId,content,groupId,type,postDate
    </sql>

    <select id="findMessageByGetUidAndType" resultMap="findSendMessageBySendUidMap" parameterType="integer">
        SELECT
        <include refid="selectContent" />
        FROM  inner_message m
        LEFT JOIN inner_message_text t
        ON m.messageId = t.id
        WHERE m.getUid = #{uid}
        And t.type = #{type}
    </select>

    <select id="findSendMessageBySendUid" resultMap="findSendMessageBySendUidMap" parameterType="integer">
        SELECT
        <include refid="selectContent" />
        FROM inner_message m
        LEFT JOIN inner_message_text t
        ON m.messageId = t.id
        where t.sendId = #{sendUid}
    </select>

    <select id="findGroupSystemMessageNotReadForGetUid" resultType="com.eendtech.witkey.model.MessageText">
        SELECT
        <include refid="systemContent"/>
        FROM
        inner_message_text as t
        WHERE
        (SELECT count(1) as num FROM inner_message as m WHERE m.messageId = t.id AND m.getUid = #{getUid}) = 0
        AND
        t.type = 0
    </select>

</mapper>

在这里讲解一下findGroupSystemMessageNotReadForGetUid,查找群发的未读系统消息通过接收者的uid,我们通过在inner_message中查找getUid是否存在,如果不存在并且inner_message_text中的id也不再inner_message中,那么这个时候返回的便是该用户接收到的系统群发的未读系统通知

具体的流程是这样的

MODEL

package com.eendtech.witkey.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.io.Serializable;

/**
 * @ author Seale
 * @ Description: 站内信实体
 * @ QQ:1801157108
 * @ Date 2020/2/3 16:22
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName(value = "inner_message")
public class Message implements Serializable {
    private static final long serialVersionUID = 7887210545783690313L;
    @TableId(type = IdType.AUTO)
    private Integer id;
    private Integer getUid;
    private String messageId;
    private String status;

    private MessageText messageText;
}
package com.eendtech.witkey.model;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.Date;

/**
 * @ author Seale
 * @ Description: 站内信内容实体
 * @ QQ:1801157108
 * @ Date 2020/2/3 16:30
 */
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
@TableName(value = "inner_message_text")
public class MessageText implements Serializable {
    private static final long serialVersionUID = -7070081047200484184L;
    @TableId(type = IdType.UUID)
    private String id ;
    private Integer sendId;
    private String content;
    private Integer type;
    private Integer groupId;
    private Date postDate;

}

Service层

package com.eendtech.witkey.service.message;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.eendtech.witkey.model.Message;
import com.eendtech.witkey.model.MessageText;
import com.eendtech.witkey.vo.MessageVO;

import java.util.List;

/**
 * @ author Seale
 * @ Description:
 * @ QQ:1801157108
 * @ Date 2020/2/3 17:03
 */
public interface MessageService extends IService<Message> {
    /***
     * 函数名: addOne
     * 函数说明: 在message中添加一条私信
     * 创建时间: 2020/2/3 17:13
     * @Param message:
     * @return: java.lang.String
     */
    String addPrivateOne(MessageVO message);

    /***
     * 函数名: addSystemOne
     * 函数说明: 添加一条系统提示信息 (点对点)
     * 创建时间: 2020/2/3 19:06
     * @Param messageVO:
     * @return: java.lang.String
     */
    String addSystemOne(MessageVO messageVO);

    /***
     * 函数名: addSystemGroup
     * 函数说明: 添加一条全体的系统提示信息
     * 创建时间: 2020/2/5 20:40
     * @Param messageVO:
     * @return: java.lang.String
     */
    String addSystemGroup(MessageVO messageVO);
    /***
     * 函数名: addNoticeOne
     * 函数说明: 添加一条站内通知信息
     * 创建时间: 2020/2/3 19:06
     * @Param messageVO:
     * @return: java.lang.String
     */
    String addNoticeOne(MessageVO messageVO);

    /***
     * 函数名: updateOne
     * 函数说明: 更新一条message,这种修改原则上只适用于站内通知,更改内容
     * 创建时间: 2020/2/3 17:17
     * @Param message:
     * @return: java.lang.String
     */
    String updateOne(MessageVO messageVO);

    /***
     * 函数名: updateOneStatus
     * 函数说明: 修改一条站内信的状态,根据getUid和messageID
     * 创建时间: 2020/2/3 17:19
     * @Param message:
     * @return: java.lang.String
     */
    String updateOneStatusByGetUidAndMessageId(MessageVO messageVO);

    /***
     * 函数名: updateAllStatus
     * 函数说明: 根据当前的getUid修改所有站内信的状态
     * 创建时间: 2020/2/3 17:20
     * @Param message:
     * @return: java.lang.String
     *
     */
    String updateAllStatusByGetUid(MessageVO messageVO);

    /***
     * 函数名: delOne
     * 函数说明: 删除一条message,根据messageID进行删除,两个表中的内容都将被删除
     * 创建时间: 2020/2/3 17:30
     * @Param message:
     * @return: java.lang.String
     */
    String delOne(MessageVO messageVO);
    String delMany(List<MessageVO> messages);

    //************查询接口***************************//

    /***
     * 函数名: findMessageByGetUidAndType
     * 函数说明: 根据站内信的接收人id和站内信的类型进行查询
     * 创建时间: 2020/2/3 17:58
     * @Param getUid:
     * @Param type:
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.eendtech.witkey.model.Message>
     */
    Page<Message> findMessageByGetUidAndType(Integer getUid , Integer type , Integer page , Integer size);
    Page<Message> findMessageByGetUidAndType(Integer getUid , Integer type , Integer page );

    /***
     * 函数名: findSendMessageBySendUid
     * 函数说明: 根据发送人id查询当前id发送的站内私信
     * 创建时间: 2020/2/3 18:00
     * @Param sendUid:
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.eendtech.witkey.model.MessageText>
     */
    Page<Message> findSendMessageBySendUid(Integer sendUid,Integer page , Integer size);
    Page<Message> findSendMessageBySendUid(Integer sendUid,Integer page );

    /***
     * 函数名: findSendNoticeMessage
     * 函数说明: 查询当前用户组的系统通知(所有的)
     * 创建时间: 2020/2/3 18:08
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.eendtech.witkey.model.MessageText>
     */
    Page<MessageText> findSendSysMessage( Integer page , Integer size);
    Page<MessageText> findSendSysMessage( Integer page );

    /***
     * 函数名: findNotceMessageNotReadForGetUid
     * 函数说明: 查询当前用户组id的未读系统通知
     * 创建时间: 2020/2/4 13:57
     * @Param getUid:
     * @Param page:
     * @Param size:
     * @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page<com.eendtech.witkey.model.MessageText>
     */

    Page<MessageText> findSystemMessageNotReadForGetUid(Integer getUid , Integer page , Integer size);
    Page<MessageText> findSystemMessageNotReadForGetUid(Integer getUid , Integer page );

    /***
     * 函数名: setGroupSystemMessageReadForGetUid
     * 函数说明: 将群发系统未读通知设置成已读
     * 创建时间: 2020/2/4 20:14
     * @Param mid:
     * @Param getUid:
     * @return: java.lang.String
     */
    String setGroupSystemMessageReadForGetUid(String mid,Integer getUid);
    /***
     * 函数名: groupSendNotice
     * 函数说明: 发送用户组站内消息
     * 创建时间: 2020/2/4 13:59
     * @Param vo:
     * @return: java.lang.String
     */

    String groupSendSystem(MessageVO vo);
}
package com.eendtech.witkey.service.message.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.mapper.MessageMapper;
import com.eendtech.witkey.model.Message;
import com.eendtech.witkey.model.MessageText;
import com.eendtech.witkey.service.message.MessageService;
import com.eendtech.witkey.service.message.MessageTextService;
import com.eendtech.witkey.utils.BaseUtils;
import com.eendtech.witkey.vo.MessageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;

/**
 * @ author Seale
 * @ Description:
 * @ QQ:1801157108
 * @ Date 2020/2/3 17:04
 */
@Transactional
@Service
public class MessageServiceImpl extends ServiceImpl<MessageMapper, Message> implements MessageService {

    @Autowired
    MessageTextService messageTextService;

    @Autowired
    MessageMapper messageMapper;

    final static Integer DEFAULT_SIZE = 10 ;
    //在message中添加一条信息

    @Override
    public String addPrivateOne(MessageVO messageVO) {
        Date now = BaseUtils.getTimeNow();

        Message message;
        MessageText text = new MessageText();

        text.setContent(messageVO.getContent())
                .setType(BaseConstant.Message.TYPE_PRIVATE.getVal())
                .setPostDate(now)
                .setSendId(messageVO.getSendUid());
        //进行text表的插入
        boolean text_flag = messageTextService.save(text);
        //获取text表中的id
        String messageId = text.getId();
        message = initMessage(messageId, messageVO.getGetUid());
        boolean message_flag = save(message);
        if (text_flag || message_flag) {
            return "发送成功";
        }

        return "发送失败";
    }

    @Override
    public String addSystemOne(MessageVO messageVO) {
        // TODO: 2020/2/5 此方案暂不实现
        return null;
    }

    @Override
    public String addSystemGroup(MessageVO messageVO) {
        Date now = BaseUtils.getTimeNow();

        //Message message;
        MessageText text = new MessageText();

        text.setPostDate(now)
                .setType(BaseConstant.Message.TYPE_SYSTEM.getVal())
                .setContent(messageVO.getContent());
        //进行text的插入
        boolean text_flag = messageTextService.save(text);
        //取出messageId
        /*String messageId = text.getId();
        message = initMessage(messageId, messageVO.getGetUid());
        boolean message_falg = save(message);*/
        if (text_flag) return "发送成功";
        return "发送失败";
    }

    @Override
    public String addNoticeOne(MessageVO messageVO) {
        Date now = BaseUtils.getTimeNow();

        Message message;
        MessageText text = new MessageText();

        text.setPostDate(now)
                .setType(BaseConstant.Message.TYPE_NOTICE.getVal())
                .setContent(messageVO.getContent());
        //进行text的插入
        boolean text_flag = messageTextService.save(text);
        //取出messageId
        String messageId = text.getId();
        message = initMessage(messageId, messageVO.getGetUid());

        boolean message_falg = save(message);
        if (text_flag || message_falg) return "发送成功";

        return "发送失败";
    }

    @Override
    public String updateOne(MessageVO messageVO) {
        String messageId = messageVO.getMessageId();
        String content = messageVO.getContent();
        MessageText text = new MessageText();
        Date now = BaseUtils.getTimeNow();
        text.setPostDate(now)
                .setContent(content)
                .setId(messageId);
        boolean text_flag = messageTextService.updateById(text);
        //此时应该在message表中修改消息状态 -> 未读
        Message message = new Message();
        message.setStatus(BaseConstant.Message.STATUS_NOTREAD.getName());
        //进行message表中消息状态的修改
        boolean message_flag =
                lambdaUpdate()
                        .eq(Message::getMessageId, messageId)
                        .update(message);

        if (text_flag || message_flag) return "修改消息成功";
        return "修改消息失败";
    }

    @Override
    public String updateOneStatusByGetUidAndMessageId(MessageVO messageVO) {
        String messageId = messageVO.getMessageId();
        Integer getUid = messageVO.getGetUid();
        Message message = new Message();

        String status = messageVO.getStatus();

        if (choiceStatus(status) != null) {
            message.setStatus(choiceStatus(status));
            boolean flag =
                    lambdaUpdate()
                            .eq(Message::getGetUid, getUid)
                            .eq(Message::getMessageId, messageId)
                            .update(message);
            if (flag) return "成功";
        }

        return "状态改变失败";
    }

    @Override
    public String updateAllStatusByGetUid(MessageVO messageVO) {
        Integer getUid = messageVO.getGetUid();
        Message message = new Message();
        String status = messageVO.getStatus();

        //进行判断,已读或者删除状态
        if (choiceStatus(status) != null) {
            message.setStatus(choiceStatus(status));
            boolean flag =
                    lambdaUpdate()
                            .eq(Message::getGetUid, getUid)
                            .update(message);
            if (flag) return "成功";
        }

        return "状态修改失败";
    }

    @Override
    public String delOne(MessageVO messageVO) {
        //// TODO: 2020/2/3 当前版本不考虑复杂后台管理故暂不实现
        return null;
    }

    @Override
    public String delMany(List<MessageVO> messages) {
        //// TODO: 2020/2/3 当前版本不考虑复杂后台管理故暂不实现
        return null;
    }

    @Override
    public Page<Message> findMessageByGetUidAndType(Integer getUid, Integer type , Integer page , Integer size) {
        //根据站内信的接收人id和站内信的类型进行查询
        if (choiceType(type) != null){
            Page<Message> p = new Page<>();
            p.setCurrent(page)
                    .setSize(size)
                    .setRecords(messageMapper.findMessageByGetUidAndType(getUid,type,p));
            return p;
        }

        return null;
    }

    @Override
    public Page<Message> findMessageByGetUidAndType(Integer getUid, Integer type, Integer page) {
        return findMessageByGetUidAndType(getUid,type,page,DEFAULT_SIZE);
    }

    @Override
    public Page<Message> findSendMessageBySendUid(Integer sendUid, Integer page , Integer size) {
        Page<Message> p = new Page<>();
        p.setCurrent(page)
                .setSize(size)
                .setRecords(messageMapper.findSendMessageBySendUid(sendUid,p));
        return p;
    }
    @Override
    public Page<Message> findSendMessageBySendUid(Integer sendUid, Integer page ) {
        return findSendMessageBySendUid(sendUid,page,DEFAULT_SIZE);
    }

    @Override
    public Page<MessageText> findSendSysMessage(Integer page, Integer size) {
        Page<MessageText> p = new Page<>();
        LambdaQueryWrapper<MessageText> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(MessageText::getType,BaseConstant.Message.TYPE_SYSTEM.getVal())
                .eq(MessageText::getGroupId,BaseConstant.Message.GROUP_TYPE_USER.getVal());
        p.setCurrent(page).setSize(size);
        return (Page<MessageText>) messageTextService.page(p,wrapper);
    }

    @Override
    public Page<MessageText> findSendSysMessage(Integer page) {
        return findSendSysMessage(page,DEFAULT_SIZE);
    }

    @Override
    public Page<MessageText> findSystemMessageNotReadForGetUid(Integer getUid, Integer page, Integer size) {
        Page<MessageText> p = new Page<>();
        p.setCurrent(page).setSize(size)
                .setRecords(messageMapper.findGroupSystemMessageNotReadForGetUid(getUid,p));
        return p;
    }

    @Override
    public Page<MessageText> findSystemMessageNotReadForGetUid(Integer getUid, Integer page) {
        return findSystemMessageNotReadForGetUid(getUid,page,DEFAULT_SIZE);
    }

    @Override
    public String setGroupSystemMessageReadForGetUid(String mid, Integer getUid) {
        Message message = new Message();
        message.setMessageId(mid)
                .setStatus(BaseConstant.Message.STATUS_READ.getName())
                .setGetUid(getUid);
        boolean flag = saveOrUpdate(message);
        if(flag) return "已读";
        return "失败";
    }

    @Override
    public String groupSendSystem(MessageVO vo) {
        MessageText text = new MessageText();
        text.setContent(vo.getContent())
                .setPostDate(BaseUtils.getTimeNow())
                .setType(BaseConstant.Message.TYPE_SYSTEM.getVal())
                .setGroupId(BaseConstant.Message.GROUP_TYPE_USER.getVal());
        boolean flag = messageTextService.saveOrUpdate(text);
        if (flag) return "发送成功";
        return "失败";
    }

    //*************************************************************************************************
    private Message initMessage(String messageId, Integer uid) {
        Message message = new Message();
        message.setStatus(BaseConstant.Message.STATUS_NOTREAD.getName())
                .setMessageId(messageId)
                .setGetUid(uid);
        return message;
    }

    private String choiceStatus(String status) {
        if (status.equals(BaseConstant.Message.STATUS_READ.getName()))
            return (BaseConstant.Message.STATUS_READ.getName());
        else if (status.equals(BaseConstant.Message.STATUS_DELETED.getName()))
            return (BaseConstant.Message.STATUS_DELETED.getName());
        return null;
    }
    private Integer choiceType(Integer type){
        if (type.equals(BaseConstant.Message.TYPE_SYSTEM.getVal()))
            return BaseConstant.Message.TYPE_SYSTEM.getVal();
        else if (type.equals(BaseConstant.Message.TYPE_PRIVATE.getVal()))
            return BaseConstant.Message.TYPE_PRIVATE.getVal();
        else if (type.equals(BaseConstant.Message.TYPE_NOTICE.getVal()))
            return BaseConstant.Message.TYPE_NOTICE.getVal();
        return null;
    }
}

简单的测试

封装一个返回工具集

package com.eendtech.witkey.utils;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * @ author Seale
 * @ Description: Json返回包集合类
 * @ QQ:1801157108
 * @ Date 2020/2/1 19:18
 */
@Getter@Setter
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
public class ReturnsKit <T>{
    //状态码
    private Integer code;
    //结果
    private T result;
    //消息
    private String message;
    //状态
    private String status;
}

再编写测试Controller

@GetMapping("/getSys/{uid}")
    public ReturnsKit<Page<MessageText>> sys (@PathVariable Integer uid){
        Page<MessageText> page = messageService.findSystemMessageNotReadForGetUid(uid,1);

        return new ReturnsKit<Page<MessageText>>().setResult(page)
                .setCode(BaseConstant.Returns.SUCCESS.getCode())
                .setStatus(BaseConstant.Returns.SUCCESS.getStatus());
    }

最后

本篇探索只适合于本人的毕设内容,不代表可以运用于生产环境,这里只是讲一个大概的思路

[tag type="primary"]控制器类还没完全写好,写好了放上来[/tag]

补上完整的Controller层

package com.eendtech.witkey.controller;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.eendtech.witkey.constants.BaseConstant;
import com.eendtech.witkey.model.Message;
import com.eendtech.witkey.model.MessageText;
import com.eendtech.witkey.service.message.MessageService;
import com.eendtech.witkey.utils.BaseUtils;
import com.eendtech.witkey.utils.ReturnsKit;
import com.eendtech.witkey.vo.MessageVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @ author Seale
 * @ Description: 信息的接口控制类
 * @ QQ:1801157108
 * @ Date 2020/2/4 17:40
 */
@RestController
@RequestMapping("/api/message")
public class MessageController {
    @Autowired
    MessageService messageService;

    @GetMapping("/getNotReadSys/{uid}")
    public ReturnsKit<Page<MessageText>> getUidNotReadSystemMessage(@PathVariable("uid") Integer uid, Integer page, Integer size) {
        //查询当前用户组 uid的未读系统通知
        size = BaseUtils.initSize(size);
        page = BaseUtils.initPage(page);
        ReturnsKit<Page<MessageText>> returnsKit = new ReturnsKit<>();

        return returnsKit.setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                .setCode(BaseConstant.Returns.SUCCESS.getCode())
                .setResult(messageService.findSystemMessageNotReadForGetUid(uid, page, size));
    }
    @GetMapping("/getReadSys/{uid}")
    public ReturnsKit<Page<Message>> getSys(@PathVariable Integer uid, Integer page , Integer size){
        page = BaseUtils.initPage(page);
        size = BaseUtils.initSize(size);
        ReturnsKit<Page<Message>> returnsKit = new ReturnsKit<>();
        if (verifyParam(uid,size,page)){
            return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                    .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                    .setResult(messageService.findMessageByGetUidAndType(uid,BaseConstant.Message.TYPE_SYSTEM.getVal(),page,size));
        }else
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
            .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
            .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());

    }

    @PostMapping("/sendGroupSysMsg")
    public ReturnsKit<String> sendGroupSystemMessage(@RequestBody MessageVO vo) {
        //发送用户组站内系统消息
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getContent())) {
            String msg = messageService.groupSendSystem(vo);
            if (msg.equals("发送成功")) {
                return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                        .setMessage(msg);
            } else {
                return returnsKit.setCode(BaseConstant.Returns.FAIL.getCode())
                        .setStatus(BaseConstant.Returns.FAIL.getStatus())
                        .setMessage(msg);
            }
        }
        return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
    }

    @PostMapping("/readGroupSysMsg")
    public ReturnsKit<String> setGroupSystemMessageRead(@RequestBody MessageVO vo) {
        //将系统群发的系统通知设置为已读
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getGetUid(), vo.getMessageId())) {
            String msg = messageService.setGroupSystemMessageReadForGetUid(vo.getMessageId(), vo.getGetUid());
            if (msg.equals("已读")) {
                return returnsKit.setMessage(msg)
                        .setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus());
            } else {
                return returnsKit.setMessage(msg)
                        .setStatus(BaseConstant.Returns.FAIL.getStatus())
                        .setCode(BaseConstant.Returns.FAIL.getCode());
            }
        } else {
            return returnsKit.setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal())
                    .setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus());
        }
    }

    @GetMapping("/getAllSysForGroup")
    public ReturnsKit<Page<MessageText>> getAllSysForGroup(Integer page, Integer size) {
        //查询当前已发送的系统通知(所有的)
        size = BaseUtils.initSize(size);
        page = BaseUtils.initPage(page);
        ReturnsKit<Page<MessageText>> returnsKit = new ReturnsKit<>();

        return returnsKit.setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                .setCode(BaseConstant.Returns.SUCCESS.getCode())
                .setResult(messageService.findSendSysMessage(page, size));
    }

    @GetMapping("/getSendPrivate/{uid}")
    public ReturnsKit<Page<Message>> getSendMessageBySendUid(@PathVariable Integer uid, Integer page, Integer size) {
        //根据发送人id查询当前id发送的站内私信
        size = BaseUtils.initSize(size);
        page = BaseUtils.initPage(page);
        ReturnsKit<Page<Message>> returnsKit = new ReturnsKit<>();
        if (verifyParam(uid)) {
            return returnsKit.setResult(messageService.findSendMessageBySendUid(uid, page, size))
                    .setCode(BaseConstant.Returns.SUCCESS.getCode())
                    .setStatus(BaseConstant.Returns.SUCCESS.getStatus());
        } else {
            return returnsKit.setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal())
                    .setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus());
        }
    }

    @GetMapping("/getPrivate/{uid}")
    public ReturnsKit<Page<Message>> getPrivateMsg(@PathVariable Integer uid, Integer page, Integer size) {
        //得到当前id收到的站内私信
        page = BaseUtils.initPage(page);
        size = BaseUtils.initSize(size);
        ReturnsKit<Page<Message>> returnsKit = new ReturnsKit<>();
        if (verifyParam(uid)) {
            return returnsKit.setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                    .setCode(BaseConstant.Returns.SUCCESS.getCode())
                    .setResult(messageService.findMessageByGetUidAndType(uid, BaseConstant.Message.TYPE_PRIVATE.getVal(), page, size));
        } else {
            return returnsKit.setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
        }
    }

    @GetMapping("/getNotice/{uid}")
    public ReturnsKit<Page<Message>> getNoticeMsg(@PathVariable Integer uid, Integer page, Integer size) {
        //得到当前id收到的站内通知
        page = BaseUtils.initPage(page);
        size = BaseUtils.initSize(size);
        ReturnsKit<Page<Message>> returnsKit = new ReturnsKit<>();
        if (verifyParam(uid)) {

            return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                    .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                    .setResult(messageService.findMessageByGetUidAndType(uid, BaseConstant.Message.TYPE_NOTICE.getVal(), page, size));
        } else {
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
        }
    }

    /***
     * 函数名: setAllNotSystemMsgRead
     * 函数说明: 当前接口设置所有的非系统通知为已读状态
     * 创建时间: 2020/2/5 16:17
     * @return: com.eendtech.witkey.utils.ReturnsKit<java.lang.String>
     */
    @PostMapping("/readNotSysAll/{uid}")
    public ReturnsKit<String> setAllNotSystemMsgRead(@PathVariable Integer uid) {

        ReturnsKit<String> returnsKit = new ReturnsKit<>();

        if (verifyParam(uid)) {
            MessageVO vo = new MessageVO();
            vo.setGetUid(uid)
                    .setStatus(BaseConstant.Message.STATUS_READ.getName());

            return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                    .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                    .setMessage(messageService.updateAllStatusByGetUid(vo));
        } else {
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
        }

    }

    @PostMapping("/readNotSys")
    public ReturnsKit<String> setNotSysMsgRead(@RequestBody MessageVO vo) {
        //设置一条站内信或者私信为已读
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getGetUid(), vo.getMessageId())) {
            String msg = messageService.updateOneStatusByGetUidAndMessageId(vo);
            if (msg.equals("成功")) {
                return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                        .setMessage(msg);
            } else {
                return returnsKit.setCode(BaseConstant.Returns.FAIL.getCode())
                        .setStatus(BaseConstant.Returns.FAIL.getStatus())
                        .setMessage(msg);
            }
        } else {
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
        }
    }

    @PostMapping("/updateSysMsg")
    public ReturnsKit<String> updateSys(@RequestBody MessageVO vo) {
        //修改一条系统通知
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getMessageId(), vo.getContent())) {
            String msg = messageService.updateOne(vo);
            if (msg.equals("修改消息成功")) {
                return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                        .setMessage(msg);
            } else {
                return returnsKit.setCode(BaseConstant.Returns.FAIL.getCode())
                        .setStatus(BaseConstant.Returns.FAIL.getStatus())
                        .setMessage(msg);
            }
        } else {
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
        }
    }

    @PostMapping("/sendNotice")
    //添加一条站内通知信息
    public ReturnsKit<String> sendNotice(@RequestBody MessageVO vo) {
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getGetUid(), vo.getContent())) {
            String msg = messageService.addNoticeOne(vo);
            if (msg.equals("发送成功")) {
                return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                        .setMessage(msg);
            } else {
                return returnsKit.setCode(BaseConstant.Returns.FAIL.getCode())
                        .setStatus(BaseConstant.Returns.FAIL.getStatus())
                        .setMessage(msg);
            }
        } else {
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
        }
    }

    @PostMapping("/sendSys")
    public ReturnsKit<String> sendSys(@RequestBody MessageVO vo) {
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getContent())) {
            String msg = messageService.addSystemGroup(vo);
            if (msg.equals("发送成功"))
                return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                        .setMessage(msg);
            else
                return returnsKit.setCode(BaseConstant.Returns.FAIL.getCode())
                        .setStatus(BaseConstant.Returns.FAIL.getStatus())
                        .setMessage(msg);
        } else
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
                    .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
                    .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
    }
    //发送私信 点对点
    @PostMapping("/sendPrivate")
    public ReturnsKit<String> sendPrivate(@RequestBody MessageVO vo){
        ReturnsKit<String> returnsKit = new ReturnsKit<>();
        if (verifyParam(vo.getContent(),vo.getGetUid(),vo.getSendUid())) {
            String msg = messageService.addPrivateOne(vo);
            if (msg.equals("发送成功"))
                return returnsKit.setCode(BaseConstant.Returns.SUCCESS.getCode())
                        .setStatus(BaseConstant.Returns.SUCCESS.getStatus())
                        .setMessage(msg);
            else
                return returnsKit.setCode(BaseConstant.Returns.FAIL.getCode())
                .setStatus(BaseConstant.Returns.FAIL.getStatus())
                .setMessage(msg);
        }else
            return returnsKit.setCode(BaseConstant.Returns.PARAM_ERRO.getCode())
            .setStatus(BaseConstant.Returns.PARAM_ERRO.getStatus())
            .setMessage(BaseConstant.ReturnMessage.PARAM_NOT_NULL.getVal());
    }

    //验证参数是否为空
    private boolean verifyParam(Object... objects) {
        for (Object o : objects) {
            if (o == null) {
                return false;
            }
        }
        return true;
    }
}

[scode type="green"]至此,一个简易的具备点对点和点对面的站内信模块就实现了!
这个时候我们通过ajax或者axios调用我们的restful接口就行了![/scode]

]]>
2 /archives/76.html#comments /feed/archives/76.html