IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【21届软件创新实验室暑假集训】java后端赛道大作业 -> 正文阅读

[Java知识库]【21届软件创新实验室暑假集训】java后端赛道大作业

设计思路

总体设计

自定义框架dataflow

上学期学了软件工程。提了一下面向数据流的设计。我就想写一个小框架。把设计好的数据流,可以无缝地转换为java代码。并且高可扩展,天然地支持逐步细化。本来受到AOP的影响。节点我都想加个切点作用的节点。结果我发现这样写很啰嗦,无穷无尽的感觉。最后我放弃了这种方案,改用子图的方式。我觉的这可以最大程度上满足需求的变化。一般来说最高层的图最抽象。需求变化基本不会影响它。最底层的function就像组成这世界的基本元素也不怎么会变。变化的最多的是中间层,也是最一团乱麻的,如果有个类管理代码块之间的联系那岂不是很好。

我写了一个模仿数据流的一个抽象对像。他有映射,链接和管道等方法。用链接把一个个函数式接口串起来,形成一个图。当需要扩展时调用原来的节点的map方法映射成一个子图就行。这样就天然支持逐步细化和迭代。或者是加个链接。如果是用ioc容器来管理和注入就更方便了。但限于时间。我还没写基于spring版本的dataFlow.

复用的

我们老师一直给我们灌输对扩展开放对修改关闭的思想。但有些时候修改代码又是必要的。以前我我们总说代码复用。为啥不数据复用呢?数据从刚开始一个抽象的语义到一个具体的语义其中可以复用的地方太多了。

关于定位错误的思考

另外关于从数据流的角度定位错误。暂不考虑错误复现的问题。我们报错的位置往往和错误的原因所在的位置相去甚远。这样我们要找到错误原因就很难,要是有个管理数据流的类,这种情况可能会大为改善。如果你有一些数据运行正常。一些数据运行异常。想象一下,数据流不断分叉,最终正确和错误数据流分道扬镳的位置极可能是错误的原因所在。准确一点说是该节点以上的节点都有可能。根据这个想法就可一写一个简陋的错误定位器(还没来得及写)。

并发

并发的管理比较简单。这抽象级别的也确实做不了啥。主要想法就是越抽象的模块之间并发程度应该越高。所以我设置了一个并发等级。每进入一个节点就减一,最后就是同步的代码。并发节点之间的数据是独立的,不需要🔓。如果你想共享一些数据可以把等级设为-1,自己管理并发。对于大量数据推荐用Java8的并发流

具体代码

  • 提供了两个工具类可以通过stream()utils()函数获得实例。
  • 一个工具函数toMermaid用来可视化,把输出张贴到Typora里。红色表示有子图的节点。
  • StreamUtilspipe函数可以串联多个Flow。但不建议。因为违背了我纯函数的设想。还是建议通过加节点和链接串联。

DATAFLOW

package cn.lyf.myblog.dataFlow;

import java.util.*;
import java.util.function.*;
import java.util.stream.*;
import java.util.concurrent.*;

public class Flow<T> {
    final private Map<String, UnaryOperator<T>> nodes = new ConcurrentHashMap<>();
    final private Map<String, List<String>> edges = new ConcurrentHashMap<>();
    final private Map<String, Flow<T>> map = new ConcurrentHashMap<>();
    final private Set<String> jump = new CopyOnWriteArraySet<>();
    // 并发等级
    final private int level;

    public static <E> Flow<E> of(Class<E> e) {
        return new Flow<E>();
    }

    public Flow() {
        this.level = 0;
    }

    public Flow(int level) {
        this.level = level;
    }

    public FlowUtils<T> utils() {
        return new FlowUtils<>(this);
    }

    public StreamUtils<T> stream() {
        return new StreamUtils<>(this);
    }

    // 设置处理节点
    public Flow<T> setHandler(String k, UnaryOperator<T> fx) {
        nodes.put(k, fx);
        return this;
    }

    // 移除处理节点
    public Flow<T> removeHandler(String k) {
        nodes.remove(k);
        return this;
    }

    // 短路处理节点
    public Flow<T> shortcut(String k) {
        if (jump.add(k))
            return this;
        else
            return null;
    }

    // 恢复处理节点
    public Flow<T> unshortcut(String k) {
        if (jump.remove(k))
            return this;
        else
            return null;
    }

    // 从K节点开始处理data
    public void start(String k, T data) {
        start(k, data, level);
    }

    // 用子流替换k节点
    public Flow<T> map(String key, Flow<T> subFlow, UnaryOperator<T> fx) {
        map(key, subFlow);
        return subFlow.setHandler(key, fx);
    }

    // 用子流细化k节点
    public Flow<T> map(String key, Flow<T> subFlow) {
        map.put(key, subFlow);
        UnaryOperator<T> fx = nodes.get(key);
        if (fx != null) {
            subFlow.setHandler(key, fx);
            for (String to : edges.getOrDefault(key, new ArrayList<>())) {
                subFlow.link(key, to);
                subFlow.map.put(to, this);
            }
        }
        return this;
    }

    // 移除子流
    public Flow<T> removemap(String key) {
        map.remove(key);
        return this;
    }

    // 链接from和to节点
    public Flow<T> link(String from, String to) {
        List<String> l = edges.get(from);
        if (l == null) {
            l = new CopyOnWriteArrayList<>();
            edges.put(from, l);
        }
        if (l.add(to))
            return this;
        return null;
    }

    // 断开from和to节点链接
    public Flow<T> unlink(String from, String to) {
        List<String> l = edges.get(from);
        if (l != null && l.remove(to))
            return this;
        return null;
    }

    // 以等级d从K节点开始处理data
    private void start(String k, T data, int d) {
        if (!jump.contains(k)) {
            Flow<T> subFlow = map.get(k);
            if (subFlow != null) {
                // 不会生成多余的线程
                subFlow.start(k, data, d - 1);
                return;
            }
            UnaryOperator<T> fx = nodes.get(k);
            if (fx == null)
                return;
            data = fx.apply(data);
            if (data == null)
                return;
        }
        List<String> list = edges.getOrDefault(k, new ArrayList<>());
        if (d < 0 || list.size() < 2) {
            for (String v : list) {
                // start(v, DeepCopy.copy(data), d - 1);
                start(v, data, d);
            }
        } else {
            final T msg = data;
            for (String v : list) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        start(v, DeepCopy.copy(msg), d - 1);
                    }
                }).start();
            }
        }
    }

    /*
     * 绘制Mermaid图像
     */
    public synchronized void toMermaid(String[] starts) {
        System.out.println("```mermaid");
        System.out.println("graph TD");
        System.out.println("classDef black fill:#222,color:#fff,stroke:#000,stroke-width:2px;");
        System.out.println("classDef red fill:#f22,color:#fff,stroke:#000,stroke-width:2px;");
        for (String s : starts)
            toMermaid(null, s);
        System.out.println("```");
    }

    private void toMermaid(String f, String s) {
        System.out.println(s + "(('" + s + "'))");
        System.out.println("class " + s + " " + (map.get(s) == null ? "black" : "red"));
        if (f != null)
            System.out.println(f + "-->" + s);
        List<String> ns = edges.get(s);
        if (ns == null)
            return;
        for (String next : ns)
            toMermaid(s, next);
    }

    /*
     * 简化操作的工具包
     */
    public static class FlowUtils<T> {
        private String last;
        final private Flow<T> flow;

        public FlowUtils<T> print() {
            return setHandler(i -> {
                System.out.println(i);
                return i;
            });
        }

        public FlowUtils(Flow<T> flow) {
            this.flow = flow;
        }

        public FlowUtils<T> start(String k, Consumer<T> fx) {
            return start(k, voidable(fx));
        }

        public FlowUtils<T> start(String k, Supplier<T> fx) {
            return start(k, voidable(fx));
        }

        public FlowUtils<T> start(String k, Runnable fx) {
            return start(k, voidable(fx));
        }

        public FlowUtils<T> start(String k, UnaryOperator<T> fx) {
            flow.setHandler(k, fx);
            return start(k);
        }

        public FlowUtils<T> start(String k) {
            last = k;
            return this;
        }

        public FlowUtils<T> link(String k) {
            flow.link(last, k);
            last = k;
            return this;
        }

        public FlowUtils<T> link(String k, Consumer<T> fx) {
            return link(k, voidable(fx));
        }

        public FlowUtils<T> link(String k, Supplier<T> fx) {
            return link(k, voidable(fx));
        }

        public FlowUtils<T> link(String k, Runnable fx) {
            return link(k, voidable(fx));
        }

        public FlowUtils<T> link(String k, UnaryOperator<T> fx) {
            flow.setHandler(k, fx);
            return link(k);
        }

        public FlowUtils<T> setHandler(Consumer<T> fx) {
            return setHandler(voidable(fx));
        }

        public FlowUtils<T> setHandler(Supplier<T> fx) {
            return setHandler(voidable(fx));
        }

        public FlowUtils<T> setHandler(Runnable fx) {
            return setHandler(voidable(fx));
        }

        public FlowUtils<T> setHandler(UnaryOperator<T> fx) {
            String[] ss = last.split("-");
            int n = 0;
            if (ss.length == 2)
                n = Integer.valueOf(ss[1]) + 1;
            String k = ss[0] + "-" + n;
            return link(k, fx);
        }

        public FlowUtils<T> map(Flow<T> f) {
            flow.map(last, f);
            return this;
        }

        // T->void
        private UnaryOperator<T> voidable(Consumer<T> fx) {
            return new UnaryOperator<T>() {
                public T apply(T t) {
                    fx.accept(t);
                    return t;
                };
            };
        }

        // void->T
        private UnaryOperator<T> voidable(Supplier<T> fx) {
            return new UnaryOperator<T>() {
                public T apply(T t) {
                    return fx.get();
                };
            };
        }

        // void->void
        private UnaryOperator<T> voidable(Runnable fx) {
            return new UnaryOperator<T>() {
                public T apply(T t) {
                    fx.run();
                    return t;
                };
            };
        }

    }

    /*
     * 流操作工具包
     */
    public static class StreamUtils<T> {
        private Stream<T> stream;
        final private Flow<T> flow;

        public StreamUtils(Flow<T> flow) {
            this.flow = flow;
        }

        public StreamUtils<T> batch(Stream<T> dataStream, String k) {
            dataStream.forEach(d -> flow.start(k, d));
            return this;
        }

        public StreamUtils<T> pipe(String k) {
            return pipe(stream, k);
        }

        public StreamUtils<T> pipe(Stream<T> dataStream, String k) {
            stream = dataStream.map(d -> {
                // 取消并发,以便于共同修改data
                flow.start(k, d, -1);
                return d;
            });
            return this;
        }

        public List<T> collect() {
            return stream.collect(Collectors.toList());
        }
    }

}

应用

在评论处理的类里我就尝试使用了下。主要是因为,为了满足范式。表有点多,有些嵌套查询。如下在初始化的时候先绑定处理函数

    final Flow<ObjectNode> flow = new Flow<>();

    @Autowired
    CommentsMapper commentsMapper;
    @Autowired
    UserInfo2Mapper userInfo2Mapper;

    public CommentService() {
        flow.setHandler("getUserInfo2", this::getUserInfo2);
        flow.setHandler("getReplys", this::getReplys);
        flow.setHandler("getReplyUser", this::getReplyUser);
    }

然后核心代码就一句话。把数据串起来。

Stream<ObjectNode> ss = list.stream().map(e -> mapper.valueToTree(e));
        List<ObjectNode> l = flow.stream().pipe(ss, "getUserInfo2").pipe("getReplys").pipe("getReplyUser").collect();

如果要扩展就把其中的节点重新注入就好了。或者分叉一下。
但是由于之前没想做管道方法,导致管道只能把数据整个往下传,没有定位功能导致复用功能大打折扣。
如下 获取评论的时候由于也要调获取用户信息。要复用只能在里面进行调用。这其实就耦合了。

 public ObjectNode getReplyUser(ObjectNode json) {
        JsonNode reply = json.get("reply");
        if (reply == null)
            return json;
        // 设计缺陷,管道没有定位功能,有耦合
        ObjectNode res = getUserInfo2(reply.deepCopy());
        json.set("reply", res);
        return json;
    }

前后端交互

总的设想就是用json接口来实现数据交互。让前后端分离。thymeleaf 模板主要用来初始化vue 或导入vue 模板。vue 负责后续请求json接口,刷新页面。

日志和错误处理

日志使用springboot 自带的slf4j
错误使用aop和@ControllerAdvic注解

权限管理

权限管理使用shiro框架进行拦截,和授权。

具体设计

数据表设计

在这里插入图片描述
基本就是一个主表加一个映射表的样子。

-- 用户表,主要用来登陆
DROP TABLE IF EXISTS `user`;
CREATE TABLE IF NOT EXISTS `user` (
  `uid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `username` VARCHAR(255) NOT NULL UNIQUE,
  `password` VARCHAR(255),
  PRIMARY KEY (`uid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 用户信息
DROP TABLE IF EXISTS `user_info`;
CREATE TABLE IF NOT EXISTS `user_info` (
  `uid` INT UNSIGNED NOT NULL UNIQUE,
  `birth` DATE DEFAULT NULL,
  `qq` VARCHAR(255),
  `wechat` VARCHAR(255),
  `mail` VARCHAR(255),
  `tel` VARCHAR(255),
  -- FOREIGN KEY (`uid`) REFERENCES `user` (`id`),
  PRIMARY KEY (`uid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
--
DROP TABLE IF EXISTS `user_info2`;
CREATE TABLE IF NOT EXISTS `user_info2` (
  `uid` INT UNSIGNED NOT NULL UNIQUE,
  `avatar` VARCHAR(255),
  `brief` VARCHAR(255),
  PRIMARY KEY (`uid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 角色表
DROP TABLE IF EXISTS `role`;
CREATE TABLE IF NOT EXISTS `role` (
  `rid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL UNIQUE,
  PRIMARY KEY (`rid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 权限表
DROP TABLE IF EXISTS `perm`;
CREATE TABLE IF NOT EXISTS `perm` (
  `pid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL UNIQUE,
  PRIMARY KEY (`pid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 用户权限映射表
DROP TABLE IF EXISTS `user_perm`;
CREATE TABLE IF NOT EXISTS `user_perm` (
  `uid` INT UNSIGNED NOT NULL,
  `pid` INT UNSIGNED NOT NULL,
  `describe` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (`uid`, `pid`) -- FOREIGN KEY (`pid`) REFERENCES `perm` (`pid`),
  -- FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 用户权角色映射表
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE IF NOT EXISTS `user_role` (
  `uid` INT UNSIGNED NOT NULL,
  `rid` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`uid`) -- FOREIGN KEY (`rid`) REFERENCES `role` (`rid`),
  -- FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 评论映射表
DROP TABLE IF EXISTS `comments`;
CREATE TABLE IF NOT EXISTS `comments` (
  `cid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `bid` INT UNSIGNED NOT NULL,
  `uid` INT UNSIGNED NOT NULL,
  `pcid` INT UNSIGNED DEFAULT NULL,
  `content` VARCHAR(255) NOT NULL,
  `date` DATE NOT NULL,
  `is_read` boolean,
  PRIMARY KEY (`cid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 博客表
DROP TABLE IF EXISTS `blog`;
CREATE TABLE IF NOT EXISTS `blog` (
  `bid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `uid` INT UNSIGNED NOT NULL,
  `title` VARCHAR(255) NOT NULL,
  `url` VARCHAR(255) NOT NULL,
  `date` DATE NOT NULL,
  `views` INT UNSIGNED DEFAULT 0,
  `likes` INT UNSIGNED DEFAULT 0,
  `favorites`INT UNSIGNED DEFAULT 0,
  `summary` VARCHAR(255),
  `attr` TINYINT DEFAULT 0,
  `url` VARCHAR(255),
  UNIQUE KEY (`uid`, `title`),
  -- FOREIGN KEY (`uid`) REFERENCES `user` (`id`),
  -- FOREIGN KEY (`cid`) REFERENCES `comments` (`cid`),
  PRIMARY KEY (`bid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 点赞映射表
DROP TABLE IF EXISTS `likes`;
CREATE TABLE IF NOT EXISTS `likes` (
  `lid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `uid` INT UNSIGNED NOT NULL,
  `bid` INT UNSIGNED NOT NULL,
  `is_read` boolean DEFAULT false,
  UNIQUE KEY (`uid`, `bid`),
  PRIMARY KEY (`lid`) -- FOREIGN KEY (`cid`) REFERENCES `comments` (`cid`),
  -- FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 收藏表
DROP TABLE IF EXISTS `favorites`;
CREATE TABLE IF NOT EXISTS `favorites` (
  `fid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `uid` INT UNSIGNED NOT NULL,
  `name` VARCHAR(255),
  `date` DATE NOT NULL,
  `total` INT UNSIGNED DEFAULT 0,
  UNIQUE KEY (`uid`, `name`),
  PRIMARY KEY (`fid`) -- FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- 收藏映射表
DROP TABLE IF EXISTS `favorites_blog`;
CREATE TABLE IF NOT EXISTS `favorites_blog` (
  `fid` INT UNSIGNED NOT NULL,
  `bid` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`fid`, `bid`) -- FOREIGN KEY (`fid`) REFERENCES `favorites` (`fid`),
  -- FOREIGN KEY (`bid`) REFERENCES `blog` (`bid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
-- SELECT uid,username,avatar,brief FROM user NATURAL LEFT JOIN `user_info2` WHERE uid=1

-- ALTER TABLE table_name ADD field_name field_type;
-- alter table blog add img varchar(255); 

DROP TABLE IF EXISTS `label`;
CREATE TABLE IF NOT EXISTS `label` (
  `lid` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255),
  `date` DATE NOT NULL,
  UNIQUE KEY (`name`),
  PRIMARY KEY (`lid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
DROP TABLE IF EXISTS `label_blog`;
CREATE TABLE IF NOT EXISTS `label_blog` (
  `lid` INT UNSIGNED NOT NULL,
  `bid` INT UNSIGNED NOT NULL,
  `uid` INT UNSIGNED NOT NULL,
  PRIMARY KEY (`lid`, `bid`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;

评论设计

template

 <template id="r">
        <div class="comment">
            <a v-if="anchor" class="avatar" :id="c.cid">
                <img :src="`/res/image/${c.user.avatar}`">
            </a>
            <a v-else class="avatar" :href="`#${c.cid}`">
                <img :src="`/res/image/${c.user.avatar}`">
            </a>
            <div class="content">
                <a class="author">
                    {{c.user.username}}
                </a>
                <a class="author" v-if="c.reply !== undefined">
                    对 {{c.reply.user.username}} 说
                </a>
                <div class="metadata">
                    <span class="date">
                        {{c.date}}
                    </span>
                </div>
                <div class="text">{{c.content}}</div>
                <div class="actions"><a class="reply">回复</a></div>
            </div>
            <div class="comments" v-if="c.reply !== undefined">
                <comment-info :c="c.reply" :anchor="false"></comment-info>
            </div>
        </div>
    </template>

vue

  var ajax = {
            data() {
                return {
                    comments: [],
                    blog: {}
                }
            },
            mounted() {
                getJson("/blog/[[${bid}]]", (a) => { this.blog = a }).then(() => {
                    var url = `/toHtml/${this.blog.url}`;
                    axios.get(url).then(response => {
                        this.blog.content = response.data;
                    })
                });
                postJson("/comments/blog", {
                    "blogId": "[[${bid}]]",
                    "pageNo": 0,
                    "pageSize": 20
                }, (a) => { this.comments = a.data });
            }
        }
        var app = Vue.createApp(ajax)
        app.component('comment-info', {
            props: ['c', 'anchor'],
            template: '#r'
        })
        app.mount('#app')

调用

<comment-info v-for="comment in comments" :c="comment" :anchor="true"></comment-info>

刚开始是想做个多级评论的,但后来想要是评论多了那岂不是要到评论外面去了?果断否定。而且两级评论不仅有利于数据库存储,也有利于查询,防止mysql嵌套查询。另一方面还要实现跳转功能,可以从回复的评论跳到被回复的评论,这里用html锚点链接外加一个anchor变量判断是否是回复。当为回复时设为跳转链接,否者设为
在这里插入图片描述

博客设计

博客为了方便编写集成了makdown编辑器。这里用的是别人的库

<dependency>
	<groupId>com.atlassian.commonmark</groupId>
	<artifactId>commonmark</artifactId>
	<version>0.17.0</version>
</dependency>

效果
在这里插入图片描述通过调用/blog/[[${bid}]]接口获取博客基本信息,再调用/toHtml/${this.blog.url}接口获取渲染后的博客内容。其中getJson是自己封装的axios方法。
getJSon

<th:block th:fragment="vue">
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/3.1.5/vue.global.prod.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/0.21.1/axios.min.js"></script>
    <script>
        function postJson(url, jsons, callback) {
            return axios.post(url, jsons, {
                header: {
                    'Content-Type': 'application/json'
                }
            }).then(response => {
                var json = response.data;
                if (json.code == 200) {
                    callback(json.result);
                } else {
                    alert(json.message);
                }
            }).catch(function (error) {
                console.log(error);
            });
        }
        function getJson(url, callback) {
            return axios.get(url).then(response => {
                var json = response.data;
                if (json.code == 200) {
                    callback(json.result);
                } else {
                    alert(json.message);
                }
            }).catch(function (error) {
                console.log(error);
            });
        }
    </script>
</th:block>
getJson("/blog/[[${bid}]]", (a) => { this.blog = a }).then(() => {
    var url = `/toHtml/${this.blog.url}`;
    axios.get(url).then(response => {
        this.blog.content = response.data;
    })
});

展示

抽象了几个展示的模板,最开始是用tympleaf 的 fragment 后来写到一半发现接口都是json接口被迫改用vue 。

在这里插入图片描述在这里插入图片描述

 <template id="ac" th:fragment="ac">
        <div class="ui segment grid stackable">
            <div class="ui five wide column">
                <a :href="`/article/${article.bid}`">
                    <img :src="`${root}res/image/${article.img}`" class="ui rounded image" alt="预览图"
                        style="height: 9em;">
                </a>
            </div>
            <div class="ui eleven wide column">
                <h3 class="ui header">{{article.title}}</h3>
                <p>{{article.summary}}</p>
                <div class="ui grid">
                    <div class="eleven wide column">
                        <div class="ui horizontal link list">
                            <div class="item">
                                <img :src="`${root}res/image/${article.author.avatar}`" alt="" class="ui avatar image">
                                <div class="content"><a href="#" class="header">{{article.author.username}}</a></div>
                            </div>
                            <div class="item">
                                <i class="calendar icon"></i>{{article.date}}
                            </div>
                            <div class="item">
                                <i class="eye icon"></i>{{article.views}}
                            </div>
                        </div>
                    </div>
                    <div class="five wide column">
                        <a :href="`/article/${article.bid}`" class="ui label teal">阅读原文</a>
                    </div>
                </div>
            </div>
        </div>
    </template>

这里查询的数据比较复杂,我用了java8的流来处理
获取最近博客列表
不过后来想想,换成vue多次请求不是更香吗?不过为了和接口文档一致只能这样了。

    public ObjectNode getBlogLately(ObjectNode json) throws Exception {
        Integer uid = getInteger(json, "/uid");
        Integer pageNo = getInteger(json, "/pageNo");
        Integer limit = getInteger(json, "/pageSize");
        ObjectNode res = mapper.createObjectNode();
        res.put("success", true);
        res.put("code", 200);
        res.put("message", "博客列表获取成功!");
        List<Blog> blogs = blogMapper.selectBlogLately(uid, limit, pageNo * limit);
        res.putPOJO("result", blogs.stream().map(this::getMoreUserInfo).collect(Collectors.toList()));
        return res;
    }

管理

在这里插入图片描述

发布

在这里插入图片描述

管理

权限

@Configuration
public class ShiroConfig {

    @Bean("UserRealm")
    public UserRealm getRealm() {
        return new UserRealm();
    }

    @Bean("mySecurityManager")
    public DefaultWebSecurityManager getMySecurityManager(@Qualifier("UserRealm") UserRealm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean getMyFactoryBean(
            @Qualifier("mySecurityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFactoryBean = new ShiroFilterFactoryBean();
        shiroFactoryBean.setSecurityManager(securityManager);
        // 过滤
        Map<String, String> filterMap = new LinkedHashMap<>();
        filterMap.put("/user/*", "user");
        filterMap.put("/content/*", "authc");
        filterMap.put("/res/**","anon");
        shiroFactoryBean.setLoginUrl("/toLogin");

        shiroFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFactoryBean;
    }


邮件

邮件工具类
邮件使用springbootspring-boot-starter-mail
用自己的qq邮箱作代理服务器,发送验证码。并用map存储。同时设置计时线程池,到时间自动移除验证码。

  // 创建计时线程池,到时间自动移除验证码
        scheduledExecutorService.schedule(new Thread(() -> {
            ele e = map.get(email);
            if (e != null && e.date.equals(date)) {
                map.remove(email);
            }
        }), 5 * 60, TimeUnit.SECONDS);
    @PostMapping("/sendCode")
    public JsonNode sendCode(@RequestBody JsonNode json) throws Exception {
        ObjectNode res = mapper.createObjectNode();
        try {
            emailuUtils.sendEmail(getString(json, "/email"));
            res.put("success", true);
            res.put("code", 200);
            res.put("message", "发送验证码成功!");
        } catch (MailException e1) {
            res.put("success", false);
            res.put("code", 471);
            res.put("message", e1.getMessage());
        } catch (MessagingException e2) {
            res.put("success", false);
            res.put("code", 472);
            res.put("message", e2.getMessage());
        }
        return res;
    }

    @PostMapping("/checkCode")
    public JsonNode verCode(@RequestBody JsonNode json) throws Exception {
        boolean b = emailuUtils.verification(getString(json, "/email"), getString(json, "/vercode"));
        ObjectNode res = mapper.createObjectNode();
        if (b) {
            res.put("success", true);
            res.put("code", 200);
            res.put("message", "验证成功!");
            return res;
        }
        res.put("success", false);
        res.put("code", 477);
        res.put("message", "验证失败!");
        return res;
    }
package cn.lyf.myblog.controller.api;

import java.util.concurrent.*;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mail.MailException;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;

@Component
public class EmailUtils {
    ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
    Map<String, ele> map = new ConcurrentHashMap<>();
    @Autowired
    JavaMailSenderImpl mailSender;

    String name="MyBlog";
    String from="MyBlog验证中心";

    class ele {
        String code;
        Date date;

        public ele(String code, Date date) {
            this.code = code;
            this.date = date;
        }
    }

    public void sendEmail(String email) throws MailException,MessagingException {
        // 验证码
        int code = (int) ((Math.random() * 9 + 1) * 1000);
        String verCode = String.valueOf(code);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        final Date date = new Date();
        String time = simpleDateFormat.format(date);
        map.put(email, new ele(verCode, date));
        // 创建计时线程池,到时间自动移除验证码
        scheduledExecutorService.schedule(new Thread(() -> {
            ele e = map.get(email);
            if (e != null && e.date.equals(date)) {
                map.remove(email);
            }
        }), 5 * 60, TimeUnit.SECONDS);
        // 一下为发送邮件部分
        MimeMessage mimeMessage = null;
        MimeMessageHelper helper = null;
        // 发送复杂的邮件
        mimeMessage = mailSender.createMimeMessage();
        // 组装
        helper = new MimeMessageHelper(mimeMessage, true);
        // 邮件标题
        helper.setSubject("【"+name+"】 注册账号验证码");
        // 因为设置了邮件格式所以html标签有点多,后面的ture为支持识别html标签
        // 想要不一样的邮件格式,百度搜索一个html编译器,自我定制。
        helper.setText("<h3>\n" + "\t<span style=\"font-size:16px;\">亲爱的用户:</span> \n" + "</h3>\n" + "<p>\n"
                + "\t<span style=\"font-size:14px;\">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style=\"font-size:14px;\">&nbsp; <span style=\"font-size:16px;\">&nbsp;&nbsp;您好!您正在进行邮箱验证,本次请求的验证码为:<span style=\"font-size:24px;color:#FFE500;\"> "
                + verCode
                + "</span>,本验证码5分钟内有效,请在5分钟内完成验证。(请勿泄露此验证码)如非本人操作,请忽略该邮件。(这是一封自动发送的邮件,请不要直接回复)</span></span>\n"
                + "</p>\n" + "<p style=\"text-align:right;\">\n"
                + "\t<span style=\"background-color:#FFFFFF;font-size:16px;color:#000000;\"><span style=\"color:#000000;font-size:16px;background-color:#FFFFFF;\"><span class=\"token string\" style=\"font-family:&quot;font-size:16px;color:#000000;line-height:normal !important;background-color:#FFFFFF;\">"+name+"</span></span></span> \n"
                + "</p>\n" + "<p style=\"text-align:right;\">\n"
                + "\t<span style=\"background-color:#FFFFFF;font-size:14px;\"><span style=\"color:#FF9900;font-size:18px;\"><span class=\"token string\" style=\"font-family:&quot;font-size:16px;color:#000000;line-height:normal !important;\"><span style=\"font-size:16px;color:#000000;background-color:#FFFFFF;\">"
                + time
                + "</span><span style=\"font-size:18px;color:#000000;background-color:#FFFFFF;\"></span></span></span></span> \n"
                + "</p>", true);
        // 收件人
        helper.setTo(email);
        // 发送方
        helper.setFrom("xxxxx@qq.com");

        // 发送邮件
        mailSender.send(mimeMessage);
        // 发送验证码成功
    }

    boolean verification(String email, String code) {
        ele e = map.get(email);
        if (e != null && e.code.equals(code)) {
            return true;
        }
        return false;
    }
}

日志及错误处理

日志主要用个aop切面实现

@Aspect
@Component
public class LogAspect {

    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Pointcut("execution(* cn.lyf.myblog.controller.*.*(..))")
    public void log() {
    }

    @Before("log()")
    public void doBeafore(JoinPoint joinPoint) {
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        System.out.println("url:" + request.getRequestURI());
        System.out.println("ip:" + request.getRemoteAddr());

        System.out.println(joinPoint.getSignature().getDeclaringTypeName());
        System.out.println(joinPoint.getSignature().getName());
        for (Object o : joinPoint.getArgs()) {
            System.out.println(o);
        }
    }

    @AfterReturning(returning = "result", pointcut = "log()")
    public void doAfterReturn(Object result) {
        logger.info("\nResult:{}", result);
    }

}

错误处理,利用ControllerAdvice注解把错误url和错误信息渲染到页面上

@ControllerAdvice
public class ControllerExceptionHandler {

    
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @ExceptionHandler(Exception.class)
    public ModelAndView exceptionHandler(HttpServletRequest req, Exception e) {
        logger.error("Request : {},Exception : {}", req.getRequestURI(), e);
        ModelAndView mv = new ModelAndView();
        mv.addObject("url", req.getRequestURI());
        mv.addObject("error", e);
        mv.setViewName("error/error");
        return mv;
    }
}
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-22 13:25:07  更:2021-08-22 13:26:05 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/20 22:54:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码