主頁 > 軟體設計 > 利用國慶搭建了一個博客網站,一起來學習一下吧!

利用國慶搭建了一個博客網站,一起來學習一下吧!

2021-10-10 08:56:27 軟體設計

Hello,你好呀,我是灰小猿,一個超會寫bug的程式猿!

利用國慶期間做了一個基于springboot+vue的前后端分離的個人博客網站,今天在這里將開發程序和大家分享一下,手把手教你搭建一個自己專屬的個人博客,

完整原始碼放置在Gitee上了,【原始碼鏈接】

小伙伴們記得?star?喲!

小伙伴們一鍵三連?關注!灰小猿帶你上高速啦🎉🎉🎉

先看一下博客網站的演示視頻:

<iframe id="wc3jH18o-1633695791839" src="https://live.csdn.net/v/embed/177928" allowfullscreen="true" data-mediaembed="csdn"></iframe>

?專案目錄?

一、個人博客網站專案整體思路

二、Java后端介面開發

(1)資料庫設計

?(2)整合MybatisPlus

(3)統一結果封裝

(4)整合shiro+jwt實作安全驗證

(5)全域例外處理

(6)物體校驗

(7)跨域問題

(8)登錄介面開發

(9)博客介面開發

三、Vue前端頁面開發

(1)安裝Element-UI

(2)安裝axios

(3)配置頁面路由

(4)登錄頁面

(5)博客串列

(6)博客編輯

(7)博客詳情頁

(8)權限路由攔截

四、寫在最后&專案總結


一、個人博客網站專案整體思路

整個專案的設計是前后端分離的,后端使用的是SpringBoot+MybatisPlus設計,前端使用Vue+ElementUI搭建頁面,安全驗證等操作由shiro安全框架完成,在進行前后端資料互動的時候采用路由傳輸,同時在前后端解決了跨域問題,博客實作登錄功能,在未登錄的情況下只能訪問博客主頁,在登錄的狀態下可以實作博客的發布與編輯功能,

整個博客主頁的博客采用時間線的方式布局,先發布的文章會在最前面展示;博客編輯功能同時支持Markdown編輯器編輯,具體的功能實作小伙伴們繼續往下看!

二、Java后端介面開發

(1)資料庫設計

在資料庫設計上主要就是兩個表,一個用戶資訊表和一個博客資訊表,

博客資訊表中的資料ID會和用戶ID相對應,詳細的表結構如下:

(2)整合MybatisPlus

平常我們使用的都是mybatis來做資料庫操作,MybatisPlus是在Mybatis的基礎上興起的,我個人的理解是它在Mybatis和逆向工程的結合,可以直接讀取我們的資料庫,并且自動的生成*Mapper.xml、Dao、Service中的代碼,提高我們的開發效率,

整合MybatisPlus的步驟如下:

第一步,匯入所需jar包

在這里我們需要匯入MybatisPlus所依賴的jar包,同時因為MybatisPlus需要涉及到代碼的自動生成,所以還需要引入freemarker的頁面模版引擎,

        <!--mp-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.2.0</version>
        </dependency>
        <!--freemarker模版引擎依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.37</version>
            <scope>runtime</scope>
        </dependency>
        <!--mp代碼生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.2.0</version>
        </dependency>

第二步、寫入組態檔

因為我們需要連接資料庫嘛,所以當然需要用到資料庫連接驅動,同時還需要在組態檔中進行配置,指定好我們的資料庫驅動、用戶名、密碼、資料庫名稱這些,

同時還需要指定好MybatisPlus掃描的xml檔案,

#配置資料庫資訊
spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/vueblog?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
    username: root
    password: ADMIN

#指定mybatisPlus掃描的xml檔案
mybatis-plus:
  mapper-locations: classpath*:/mapper/**Mapper.xml

第三步、開啟mapper介面掃描,添加分頁插件

在這里需要實作一個分頁插件PaginationInterceptor,使用該分頁插件的目的很簡單,就是讓我們在每次查詢到的結果以分頁的形式展示出來,該插件是寫在MybatisPlusConfig類下的,

同時還有一點需要注意的是,在添加該組態檔的時候我們需要在類上增加@MapperScan("")注解,在其中傳入我們想要將介面寫入到的包名,該介面的目的就是執行想要變成實作類的介面所在的包,如@MapperScan("com.gyg.mapper")

/**
 * mybatisPlus配置
 */
@Configuration
@EnableTransactionManagement
@MapperScan("com.gyg.mapper")   //指定變成實作類的介面所在的包
public class MybatisPlusConfig {

    /**
     * 實作一個分頁插件PaginationInterceptor
     * @return
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }

}

第四步、生成相關代碼

想要通過mybatisplus生成代碼,官方是給了我們一個工具類的,通過該工具類,我們可以寫入自己的引數,然后就可以自動的生成相關的代碼了,

工具類名叫:CodeGenerator ,使用時我們需要將其和springboot的啟動類放置在同級目錄下,啟動運行之后,輸入我們想要生成對應代碼的表名即可,

工具類的代碼比較長,我放置在了gitee上,【原始碼鏈接】

運行這個代碼生成器我們就可以自動的生成相關資料表的mapper、dao、service等內容了!

現在資料庫相關的代碼已經是基本完成了,

(3)統一結果封裝

由于我們的資料都是需要通過json串的形式回傳給我們的前端頁面的,所以我們就需要對回傳的結果進行一個統一的封裝,在這里我們可以自定義一個封裝類Result,方便我們將資料以統一的格式回傳出去,

該封裝類中一般需要回傳的資訊有三個:

  • 狀態碼code(如200表示操作正確,400表示例外)
  • 結果訊息msg
  • 結果資料data

同時在封裝類中定義全域方法,用于在不同的狀態下回傳不同的資料,封裝類的代碼如下:

import lombok.Data;

import java.io.Serializable;

/**
 * 封裝一個回傳統一格式資料的結果集
 */
@Data
public class Result implements Serializable {

    private int code;   //200正常、非200例外
    private String msg;     //提示資訊
    private Object data;    //回傳資料

    public static Result success(Object data) {
        return success(200,"操作成功",data);
    }

    /**
     * 訊息回傳方法
     *
     * @param code
     * @param msg
     * @param data
     * @return
     */
    public static Result success(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }


    public static Result fail(String msg) {
        return fail(400,msg,null);
    }

    public static Result fail(String msg, Object data) {
        return fail(400,msg,data);
    }

    public static Result fail(int code, String msg, Object data) {
        Result r = new Result();
        r.setCode(code);
        r.setMsg(msg);
        r.setData(data);
        return r;
    }

}

(4)整合shiro+jwt實作安全驗證

在進行安全驗證的時候我采用的是shiro+jwt結合的方式,大概驗證思路是這樣的:

前端將登陸資訊傳送過來之后,通過shiro的Realm進行安全驗證,如果驗證不通過,那么直接將錯誤資訊回傳到前端,如果登錄資訊驗證通過,就將用戶資訊存盤到服務器端,然后通過jwtUtils工具類根據用戶的ID生成一個token,并且將該token放入回傳請求的請求頭中,攜帶給瀏覽器,瀏覽器在接收到服務器的回傳的請求的時候,就會決議并獲取到該token,并將該token存盤到本地;

這樣在瀏覽器每次向服務器發送請求的時候都會從本地攜帶上該token,服務器也會對每次瀏覽器發送的請求進行驗證,驗證瀏覽器回傳的token和服務器端保存的token是否相同,如果相同就放行進行處理;如果不相同就將錯誤資訊回傳到瀏覽器,

附上一個請求程序的圖示:

安全驗證所用到的類有:

  1. ShiroConfig:用于配置shiro的驗證資訊
  2. AccountRealm:用于對瀏覽器回傳的登錄資訊進行驗證
  3. JwtToken:封裝和獲取token中的資料
  4. AccountProfile:登錄之后回傳的用戶資訊的一個載體
  5. JwtFilter:jwt過濾器,用于過濾瀏覽器的請求

其中的代碼比較多,我就放置在的Gitee上,小伙伴們可以在其中獲取【原始碼鏈接】

(5)全域例外處理

無論我們平常在進行什么樣的專案開發,進行全域例外處理都是一個非常好的習慣,進行全域例外處理,它可以將我們的錯誤資訊用最簡單的方式表示出來,并不會出現大量的報錯資訊,方便我們查閱,在這里我宣告了幾個在專案中經常會遇到的報錯資訊,

/**
 * 例外處理工具類
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    /**
     * 運行時例外
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)     //判斷回傳訊息是否正常
    @ExceptionHandler(value = RuntimeException.class)
    public Result handler(RuntimeException e){
        log.error("運行時例外---------->>>" + e);
        return Result.fail(e.getMessage());
    }

    /**
     * shiro運行例外
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.UNAUTHORIZED)     //判斷回傳訊息是否正常,沒有權限例外
    @ExceptionHandler(value = ShiroException.class)
    public Result handler(ShiroException e){
        log.error("shiro例外---------->>>" + e);
        return Result.fail(401,e.getMessage(),null);
    }

    /**
     * 物體校驗例外
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)     //判斷回傳訊息是否正常,沒有權限例外
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Result handler(MethodArgumentNotValidException e){
        log.error("物體檢驗例外例外---------->>>" + e);
        BindingResult bindingResult = e.getBindingResult();
        ObjectError objectError = bindingResult.getAllErrors().stream().findFirst().get();
        return Result.fail(objectError.getDefaultMessage());
    }

    /**
     * 處理斷言例外
     * @param e
     * @return
     */
    @ResponseStatus(HttpStatus.BAD_REQUEST)     //判斷回傳訊息是否正常,沒有權限例外
    @ExceptionHandler(value = IllegalArgumentException.class)
    public Result handler(IllegalArgumentException e){
        log.error("斷言例外例外---------->>>" + e);
        return Result.fail(e.getMessage());
    }

}

(6)物體校驗

在表單資料提交的時候,我們通常會對資料進行校驗,比如不能為空,或長度不能小于指定值等,在前端我們可以通過js插件來完成,但是如果在后端的話,我們可以通過使用Hibernate validatior的方式來進行校驗,

在springboot中已經自動集成了Hibernate validatior的校驗,我們只需要在代碼中直接使用就可以了,

所以我們只需要在物體的屬性上添加相應的校驗規則就可以了,比如在user實體類中:


/**
 *
 * @author 關注公眾號:碼猿編程日記
 * @since 2021-09-21
 */
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@TableName("m_user")
public class User implements Serializable {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    @NotBlank(message = "用戶名不能為空")
    private String username;

    private String avatar;

    @NotBlank(message = "郵箱不能為空")
    @Email(message = "郵箱格式不正確")
    private String email;


    private String password;

    private Integer status;

    private LocalDateTime created;

    private LocalDateTime lastLogin;


}

(7)跨域問題

由于我們做的是前后端分離的專案,所以在請求發送上一定會出現同源策略的相關問題,這就需要我們解決跨域問題了,關于在前后端互動中解決跨域問題,我專門寫了一篇博客,小伙伴們可以去看那一篇《SpringBoot與Vue互動解決跨域問題》

在springboot的后端解決跨域問題的策略比較簡單,只需要添加一個類CorsConfig,并且讓它實作WebMvcConfigurer介面, 其中代碼如下,一般在開發的時候直接將代碼復制過去就可以了,

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * 解決跨域問題
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders("*");

    }
}

(8)登錄介面開發

登錄介面的開發思路很簡單,就是接收前端發送過來的登錄資訊,進行驗證是否通過,同時還有一個退出登錄的介面,傳入用戶的資訊,確定是在登錄狀態時可以實作退出登錄操作,

代碼如下;

@RestController
public class AccountController {

    @Autowired
    UserService userService;

    @Autowired
    JwtUtils jwtUtils;


    @PostMapping("/login")
    public Result login(@Validated @RequestBody LoginDto loginDto, HttpServletResponse response) {

        System.out.println("用戶名和密碼:" + loginDto.getUsername() + " " + loginDto.getPassword());
//        獲取到當前用戶
        Subject subject = SecurityUtils.getSubject();
//        封裝用戶名和密碼
        UsernamePasswordToken token = new UsernamePasswordToken(loginDto.getUsername(), loginDto.getPassword());

        System.out.println("封裝用戶名和密碼成功!!!");

        try {
//            使用shiro進行用戶驗證
            subject.login(token);
//            如果驗證通過再根據用戶名查找到該用戶
            User user = userService.getOne(new QueryWrapper<User>().eq("username", loginDto.getUsername()));
            Assert.notNull(user, "用戶不存在!");

            if (!user.getPassword().equals(loginDto.getPassword())) {
                return Result.fail("密碼錯誤!");
            }
//            根據用戶id生成一個jwt
            String jwt = jwtUtils.generateToken(user.getId());

//            將jwt寫入
            response.setHeader("authorization", jwt);
            response.setHeader("Access-Control-Expose-Headers", "authorization");

            //            如果正確就回傳用戶資訊
            return Result.success(MapUtil.builder()
                    .put("id", user.getId())
                    .put("username", user.getUsername())
                    .put("avatar", user.getAvatar())
                    .put("email", user.getEmail())
                    .map()
            );
        } catch (UnknownAccountException e) {
            return Result.fail("用戶不存在2");
        } catch (IncorrectCredentialsException e) {
            return Result.fail("密碼不正確2");
        }
    }

    /**
     * 退出登錄
     *
     * @return
     */
    @RequiresAuthentication
    @GetMapping("/logout")
    public Result logout() {
        Subject subject = SecurityUtils.getSubject();
//        AccountProfile profile = (AccountProfile) subject.getPrincipal();
//        System.out.println(profile.getId());
//        會請求到logout
        subject.logout();

        return Result.success("退出成功");
    }

    @RequiresAuthentication
    @GetMapping("/testlogin")
    public Result testlogin() {
        User user = userService.getById(1L);
        return Result.success(user);
    }


}

(9)博客介面開發

博客介面中主要實作的功能有:回傳主頁資訊,回傳指定博客資訊,編輯和發布博客、洗掉博客的功能,其中編輯和洗掉博客只有在登錄狀態下才能請求成功,其他兩個請求無需進行登錄,

代碼如下:

/**
 * @author 關注公眾號:碼猿編程日記
 * @since 2021-09-21
 */
@RestController
//@RequestMapping("/blog")
public class BlogController {

    @Autowired
    BlogService blogService;

    /**
     * 分頁博客頁
     *
     * @param currentPage
     * @return
     */
    @GetMapping("/blogs")
    public Result list(@RequestParam(defaultValue = "1") Integer currentPage) {
        Page page = new Page(currentPage, 5);

        AccountProfile accountProfile =  (AccountProfile) SecurityUtils.getSubject().getPrincipal();
        System.out.println(accountProfile);

        IPage<Blog> pageDate = blogService.page(page, new QueryWrapper<Blog>().orderByDesc("created"));

        return Result.success(pageDate);
    }

    /**
     * 查找指定的博客
     *
     * @param id
     * @return
     */
    @GetMapping("/blog/{id}")
    public Result detail(@PathVariable(name = "id") long id) {

        Blog blog = blogService.getById(id);
//        用斷言來來判斷文章是否找不到
        Assert.notNull(blog, "該博客已經被洗掉!");

//        回傳該博客資料
        return Result.success(blog);
    }

    /**
     * @param blog
     * @return
     */
//    只有登錄之后才能編輯
    @RequiresAuthentication
    @PostMapping("/blog/edit")
    public Result edit(@Validated @RequestBody Blog blog) {

        System.out.println("編輯測驗11111111111111111");
        System.out.println(blog.toString());
        System.out.println("當前用戶ID:" + ShiroUtil.getProfile().getId());
        System.out.println(blog.toString());
//        System.out.println("當前用戶id:" + ShiroUtil.getSubjectID());

        Blog temp = null;
        //      如果博客id不為空,就是編輯
        if (blog.getId() != null) {
            temp = blogService.getById(blog.getId());
//            每一個用戶只能編輯自己的文章
            Assert.isTrue(temp.getUserId().equals(ShiroUtil.getProfile().getId()), "你沒有權限編輯");

        } else {
            //      如果id為空,就是添加
            temp = new Blog();
//            將這篇文章添加給當前用戶的id
            temp.setUserId(ShiroUtil.getProfile().getId());
//            博客創建時間
            temp.setCreated(LocalDateTime.now());
            temp.setStatus(0);
        }

        //  將兩個物件進行復制,指定那些欄位不復制
        //BeanUtil.copyProperties("轉換前的類","轉換后的類");
        BeanUtil.copyProperties(blog, temp, "id", "userId", "created", "status");

        //保存或者更新這一篇文章
        blogService.saveOrUpdate(temp);

        return Result.success("操作成功");
    }

    /**
     * 根據博客ID洗掉博客
     * @param id
     * @return
     */
    @RequiresAuthentication
    @PostMapping("/blog/delete/{id}")
    public Result deleteBlog(@PathVariable("id") long id){
        System.out.println(id);
        System.out.println("------------");
//        int bid = Integer.parseInt(id);
        boolean isRemove = blogService.removeById(id);
        if (!isRemove){
            return Result.fail("洗掉失敗!");
        }
        return Result.success("洗掉成功!");
    }

}

以上就是我們后臺介面開發的全部程序,在開發完成之后需要進行相關的介面測驗,測驗完成無誤之后就可以進行前臺頁面的開發了,

三、Vue前端頁面開發

前端頁面的開發我們是基于Vue和Element-Ui的,同時涉及axios發送請求,markdown編輯器的引入、登錄驗證、跨域請求等問題,

博客主頁的頁面是這樣的:

接下來和大家分享一下前端頁面的開發流程,

(1)安裝Element-UI

Element-UI是進行前端開發的一個組件庫,官網地址,這里面提供了各種已經開發好的組件供我們使用,

Element - The world's most popular Vue UI framework

使用該組件庫我們首先是需要引入的,在vue的根目錄下,輸入如下命令:

# 切換到專案根目錄

cd vueblog-vue

# 安裝element-ui

npm install element-ui --save

之后打開專案的src目錄下的main.js檔案,引入Element-UI依賴,

import Element from 'element-ui'

import "element-ui/lib/theme-chalk/index.css"

Vue.use(Element)

到現在,組件庫中的組件我們就可以任意使用了,

(2)安裝axios

axios是一個基于promise的HTTP庫,在我們進行前后端專案開發的時候,使用該工具可以提高我們的開發效率,【axios官網】

Axios的安裝命令如下:

cnpm install axios --save

同樣需要在main.js中全域引入axios,

import axios from 'axios'

Vue.prototype.$axios = axios

之后我們就可以通過this.$axios.get()來發起我們的請求了!

(3)配置頁面路由

接下來是定義頁面路由,定義頁面路由的目的是我們在訪問相應路徑的時候,可以根據路由來確定到我們將要訪問的頁面,

在views文件夾中的頁面有:

  • BlogDetail.vue(博客詳情頁)
  • BlogEdit.vue(編輯博客)
  • Blogs.vue(博客串列)
  • Login.vue(登錄頁面)

頁面路由設定在router檔案下的index.js中,配置如下:

/**
 * 路由注冊中心
 */

import Vue from 'vue'
import VueRouter from 'vue-router'


//注冊頁面
import Login from '../views/Login.vue'
import Blogs from '../views/Blogs.vue'
import BlogEdit from '../views/BlogEdit.vue'
import BlogDetail from '../views/BlogDetail.vue'


Vue.use(VueRouter)

const routes = [
    {
        path: '/',
        name: 'index',
        redirect: {name: "Blogs"}   //頁面重定向
    },
    {
        path: '/blogs',
        name: 'Blogs',
        component: Blogs
    },
    {
        path: '/login',
        name: 'Login',
        component: Login
    },
    {
        path: '/blog/add',
        name: 'BlogAdd',
        component: BlogEdit,
        //添加權限訪問,表示只有登錄之后才能進行該操作
        meta: {
            requireAuth: true
        }
    },
    {
        path: '/blog/:blogId/edit',
        name: 'BlogEdit',
        component: BlogEdit,
        //添加權限訪問,表示只有登錄之后才能進行該操作
        meta: {
            requireAuth: true
        }
    },
    {
        path: '/blog/:blogId',
        name: 'BlogDetail',
        component: BlogDetail
    },

]

const router = new VueRouter({
    mode: 'history',
    base: process.env.BASE_URL,
    routes
})

export default router

在上述代碼中帶有meta:requireAuth: true說明是需要登錄之后才能訪問的受限資源,后面我們路由權限攔截時候會講到這個,

(4)登錄頁面

登錄頁面我們這里是由用戶名和密碼進行登錄的,組件我采用了element-ui中的組件,所以在登錄中直接就有了登錄校驗的功能,

?

登錄驗證

在這里點擊登錄按鈕之后,會有一個驗證登錄的程序,簡單說一下驗證的思路,

我們發起登錄請求之后,獲取到它回傳的請求,查看請求中是否存在我們需要的jwttoken,如果存在的,那么我們就將獲取到的token和用戶資訊共享給我們的瀏覽器,之后跳轉到主頁,如果不存在,就彈窗提示,并且不做任何操作,

代碼如下:

        methods: {

            /**提交表單**/
            async submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        // alert('submit!');
                        //提交登錄資訊
                        //獲取到當前的this物件
                        const _this = this;
                        this.$axios.post("/login", this.ruleForm).then(res => {

                            console.log(res.data)
                            const jwt = res.headers["authorization"]
                            if (jwt === null){
                                this.$alert('用戶名或密碼錯誤!!', '提示', {
                                    confirmButtonText: '確定',
                                    callback: action => {
                                        // _this.$router.push("/blogs")
                                    }
                                });
                            }else {
                                const userInfo = res.data.data
                                console.log(jwt)
                                console.log(userInfo)

                                //把資料共享出去
                                _this.$store.commit("SET_TOKEN", jwt);
                                _this.$store.commit("SET_USERINFO", userInfo);

                                //獲取
                                console.log(_this.$store.getters.getUser)
                                //頁面跳轉
                                _this.$router.push("/blogs")
                            }


                        });
                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            }
        }

token狀態同步

在上述代碼中,我們用到了$store來同步token和用戶資訊,那么這個同步是如何完成的呢,其實是我們在store檔案下的index.js中進行了封裝和設定,

存盤token,我們用的是localStorage,存盤用戶資訊,我們用的是sessionStorage,畢竟用戶資訊我們不需要長久保存,保存了token資訊,我們隨時都可以初始化用戶資訊,

index.js中的代碼如下:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
    state: {
        // token: "",
        //用戶的資訊可以直接從瀏覽器中取出來
        token: localStorage.getItem("token"),
        //反序列化操作
        userInfo: JSON.parse(sessionStorage.getItem("userInfo"))
    },
    mutations: {
        /**類似set操作*/
        //給token賦值
        SET_TOKEN: (state, token) => {
            state.token = token;
            //將資訊存盤到瀏覽器中,以至于瀏覽器關閉時資訊還在
            localStorage.setItem("token", token);
        },
        //給userinfo賦值
        SET_USERINFO: (state, userInfo) => {
            state.userInfo = userInfo;
            //session會在每次瀏覽器關閉時清空,在重新登錄后再生成
            //由于sessionStorage不能存盤物件,所以要將其存盤成字串的形式
            sessionStorage.setItem("userInfo", JSON.stringify(userInfo));
        },
        //移除用戶資訊
        REMOVE_INFO: (state) => {
            //移除用戶資訊時將用戶所有的資訊都置為空
            state.token = "";
            state.userInfo = {};
            localStorage.setItem("token", "");
            sessionStorage.setItem("userInfo", JSON.stringify(""));
        }

    },
    getters: {
        /**類似get請求*/
        //獲取用戶資訊
        getUser: state => {
            return state.userInfo;
        }
    },
    actions: {},
    modules: {}
})

定義全域axios攔截器

由于我們的登錄有時候會出現密碼輸出錯誤的情況,雖然不需要做任何操作,但是有時候我們還是需要進行彈窗提示,這樣對于這種錯誤資訊的彈窗,我們就可以對其進行統一的封裝和設定,所以我對axios設定一個攔截器,包括前置攔截和后置攔截,如果說我們回傳資料的code或者status不正常就會彈窗提示相應的資訊,

操作是在在src目錄下創建一個檔案axios.js(與main.js同級),定義axios的攔截:

import axios from "axios"
import Element from "element-ui"
import router from "../router"
import store from "../store";


//設定統一請求路徑
axios.defaults.baseURL = "/api"
//前置攔截
axios.interceptors.request.use(config => {
    return config
})

/**
 * 對請求的回傳資料進行過濾
 */
axios.interceptors.response.use(response => {
    let res = response.data;

    console.log("=================")
    console.log(res)
    console.log("=================")

    //如果狀態碼是200,直接放行
    if (res.code === 200) {
        return response
    } else {
        //如果是用戶名錯誤會直接斷言處理,不會到達這一步!
        //彈窗提示!
        Element.Message.error('用戶名或密碼錯誤!', {duration: 3 * 1000})
        //回傳錯誤資訊
        return Promise.reject(response.data.msg)
    }
},
    //如果是非密碼錯誤,會到達這一步
    error => {
        console.log(error)
        //如果回傳的資料里面是空
        if (error.response.data){
            error.message = error.response.data.msg;
        }
        //如果狀態碼是401,
        if (error.response.status === 401){
            store.commit("REMOVE_INFO")
            router.push("/login")
        }

        //彈出錯誤資訊
        Element.Message.error(error.message, {duration: 3 * 1000})
        return Promise.reject(error)
})


之后別忘了在main,js檔案中匯入axios,js檔案,

import './axios.js' // 請求攔截

簡單說一下這幾個攔截的作用:

前置攔截:在請求之前的攔截,可以在其中統一為所有需要權限的請求裝配上header的token資訊,這樣就不要在使用的時候再配置,

后綴攔截:在請求回傳之后的攔截,可以在請求之后對回傳的資料進行處理和驗證,

(5)博客串列

在我們登錄完成之后就會進入了博客的主頁面,在該頁面主要是展示了當前錄入到系統中的博客資訊,界面如下:

?

整個博客的顯示是按照時間線的方式展開的,最后發布的博客會在第一個出現,同時你會發現在博客主頁的頭部會展示我們的一些基本資訊,包括個人資訊以及編輯和退出的功能,這個頭部資訊會一直顯示在我們的頁面中,所以為了能夠實作代碼復用,減少代碼的使用量,我們將頭部資訊全部都抽取了出來,放置在了Header.vue頁面中,

<template>
    <div class="m_content">
        <h3>歡迎來到{{user.username}}的博客</h3>
        <div class="block">
            <el-avatar :size="50" :src="user.avatar"></el-avatar>
            <div>{{user.username}}</div>
        </div>

        <div class="maction">
            <span><el-link type="primary" href="/blogs">主頁</el-link></span>

            <el-divider direction="vertical"></el-divider>
            <span><el-link type="success" href="/blog/add">發表博客</el-link></span>

            <span v-show="!haslogin">
                <el-divider direction="vertical"></el-divider>
                <span><el-link type="warning" href="/login">登錄</el-link></span>
            </span>

            <span v-show="haslogin">
                <el-divider v-show="haslogin" direction="vertical"></el-divider>
                <span ><el-link type="danger" @click="logout">退出</el-link></span>
            </span>

        </div>
    </div>
</template>

<script>

    export default {
        name: "Header",
        data() {
            return {
                user: {
                    username: "請先登錄",
                    avatar: "https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png"
                },
                haslogin: false
            }
        },
        //寫入方法
        methods: {
            //退出登錄
            logout() {
                const _this = this;
                //發送退出登錄請求
                _this.$axios.get("/logout", {
                    //由于只有在登錄的時候才能進行退出,所以將token資訊攜帶回去
                    headers: {
                        "authorization": localStorage.getItem("token")
                    }
                }).then(res => {
                    //清空資料資訊
                    _this.$store.commit("REMOVE_INFO")
                    //回傳到登錄界面
                    _this.$router.push("/login")
                })
            },
        },
        //執行一個初始化操作
        created() {
            //如果用戶名存在,就回顯出來
            if (this.$store.getters.getUser.username) {
                this.user.username = this.$store.getters.getUser.username
                this.user.avatar = this.$store.getters.getUser.avatar
                this.haslogin = true
            }
        }
    }
</script>

<style scoped>
    .m_content {
        max-width: 960px;
        margin: 0 auto;
        text-align: center;
    }

    .maction {
        margin: 10px 0px;
    }
</style>

如果在其他頁面中需要該頭部資訊時,只需要將Header頁面參考到該頁面中,之后在內容中寫入即可,如下:

import Header from "@/components/Header";

data() {

  components: {Header}

}

然后模板中呼叫組件

<Header></Header>

在該博客主頁中包括博客分頁,以及博客排列,因為我們使用了分頁組件,所以在回傳的資訊會直接帶有分頁資訊,我們直接拿來用就可以了,

<template>
    <div>
        <Header></Header>
        <div class="block">
            <el-timeline>
                <el-timeline-item :timestamp="blog.created" placement="top" v-for="blog in blogs">
                    <el-card>
                        <router-link :to="{name:'BlogDetail',params:{blogId:blog.id}}">
                            <h4>{{blog.title}}</h4>
                        </router-link>
                        <p>{{blog.description}}</p>
                    </el-card>
                </el-timeline-item>


            </el-timeline>
        </div>
        <el-pagination class="mpage"
                       background
                       layout="prev, pager, next"
                       :current-page="currentPage"
                       :page-size="pageSize"
                       :total="total"
                       @current-change=page
        >
        </el-pagination>
    </div>
</template>

<script>

    //匯入公共的Header
    import Header from "../components/Header";

    export default {
        name: "Blogs",
        //將Header注冊進去
        components: {Header},
        //回傳的資料
        data() {
            return {
                blogs: {},
                currentPage: 1,  //當前頁
                total: 0,    //總共多少頁
                pageSize: 5  //每一頁的資料個數
            }
        },
        mounted() {
        },
        methods: {
            //請求指定頁的方法
            page(currentPage) {
                const _this = this;
                _this.$axios.get("/blogs?currentPage=" + currentPage,{
                    headers: {
                        "authorization": localStorage.getItem("token")
                    }
                }).then(res => {
                    console.log(res)
                    //從獲取到的資料中進行賦值
                    _this.blogs = res.data.data.records
                    _this.currentPage = res.data.data.current
                    _this.total = res.data.data.total
                    _this.pageSize = res.data.data.size
                })
            }
        },
        created() {
            this.page(1)
        }
    }
</script>

<style scoped>
    .mpage {
        margin: 0 auto;
        text-align: center;
    }
</style>

data()中直接定義博客串列blogs、以及一些分頁資訊,methods()中定義分頁的呼叫介面page(currentPage),引數是需要調整的頁碼currentPage,得到結果之后直接賦值即可,然后初始化時候,直接在mounted()方法中呼叫第一頁this.page(1),

(6)博客編輯

博客編輯頁面中我們可以對已經發布的博客進行編輯,也可以發布新的博文,但是該項功能是只有在登錄的狀態下才能使用的,在博客編輯頁面中,我們引入了markdown編輯器,該編輯器有關于vue的支持,我們直接匯入相關依賴拿來用就可以了,

Markdown編輯器引入

第一步、進入插件

Markdown編輯器中比較好用的插件是mavon-editor,首先我們需要安裝相關插件,

cnpm install mavon-editor --save

第二步、全域注冊

引入之后如果想要使用,當然是需要在main.js檔案中全域注冊的,

// 全域注冊
import Vue from 'vue'
import mavonEditor from 'mavon-editor'
import 'mavon-editor/dist/css/index.css'

// use
Vue.use(mavonEditor)

第三步、定義到頁面中

Markdown編輯器的使用,在注冊到全域頁面中之后,只需要我們在頁面中使用如下代碼引入即可,

<mavon-editor v-model="editForm.content"/>

以上就是vue引入markdown編輯器的步驟了,

另外附上博客編輯頁面的代碼:

<template>
    <div>
        <Header></Header>
        <div class="m_content">
            <el-form :model="ruleForm" :rules="rules" ref="ruleForm" label-width="100px" class="demo-ruleForm">
                <el-form-item label="標題" prop="title">
                    <el-input v-model="ruleForm.title"></el-input>
                </el-form-item>

                <el-form-item label="摘要" prop="description">
                    <el-input type="textarea" v-model="ruleForm.description"></el-input>
                </el-form-item>

                <el-form-item label="內容" prop="content">
                    <mavon-editor v-model="ruleForm.content"></mavon-editor>
                </el-form-item>

                <el-form-item>
                    <el-button type="primary" @click="submitForm('ruleForm')">立即發布</el-button>
                    <el-button @click="resetForm('ruleForm')">重置</el-button>
                </el-form-item>
            </el-form>
        </div>
    </div>
</template>

<script>
    import Header from "../components/Header";

    export default {
        name: "BlogEdit",
        components: {Header},
        data() {
            return {
                ruleForm: {
                    id: "",
                    title: '',
                    description: '',
                    content: '',
                },
                rules: {
                    title: [
                        {required: true, message: '請輸入標題', trigger: 'blur'},
                        {min: 5, max: 100, message: '長度在 5 到 100 個字符', trigger: 'blur'}
                    ],
                    description: [
                        {required: true, message: '請輸入摘要', trigger: 'blur'}
                    ],
                    content: [
                        {required: true, message: '請輸入內容', trigger: 'blur'}
                    ],

                }
            };
        },
        mounted() {
            console.log(localStorage.getItem("token"))
        },
        methods: {
            submitForm(formName) {
                this.$refs[formName].validate((valid) => {
                    if (valid) {
                        const _this = this
                        console.log(this.ruleForm)
                        //發送編輯的請求
                        _this.$axios.post("/blog/edit", this.ruleForm, {
                            //添加請求頭部token
                            headers: {
                                "authorization": localStorage.getItem("token")
                            }
                        }).then(res => {
                            console.log(res)
                            this.$alert('編輯操作成功!', '提示', {
                                confirmButtonText: '確定',
                                callback: action => {
                                    _this.$router.push("/blogs")
                                }
                            });
                        })

                    } else {
                        console.log('error submit!!');
                        return false;
                    }
                });
            },
            resetForm(formName) {
                this.$refs[formName].resetFields();
            }
        },
        //頁面渲染前的后置方法
        created() {
            //將博客資訊回顯,作用在進行編輯的時候可以在編輯頁面出現用戶的資訊
            //用戶發送“/blog/{id}/edit”型別的請求才會回顯
            //用戶發送“/blog/add”添加博客的請求時,獲取不到值
            const blogId = this.$route.params.blogId
            console.log(blogId)
            const _this = this;
            if (blogId){
                _this.$axios.get("/blog/" + blogId).then(res=>{
                    const blog = res.data.data;
                    _this.ruleForm.id = blog.id
                    _this.ruleForm.title = blog.title
                    _this.ruleForm.description = blog.description
                    _this.ruleForm.content = blog.content

                })
            }
        }
    }
</script>

<style scoped>
    .m_content {
        margin: 0 auto;
    }
</style>

效果如下:

(7)博客詳情頁

在博客詳情頁中,我們需要回顯我們的博客資訊,但是我們在發布博客的時候,使用的是markdown編輯器,所以在回顯的時候,我們回顯的內容帶有markdown標簽的,那么應該如何回顯我們正式編輯的文本呢?

在這里需要使用一個插件markdown-it,它的作用是決議md檔案,之后再匯入github-markdown-it,使用md樣式,

# 用于決議md檔案

cnpm install markdown-it --save

# md樣式

cnpm install github-markdown-css

在需要寫入博客文本的地方的使用方法是:

<div class="content markdown-body" v-html="blog.content"></div>

其中還涉及到一些md相關的渲染,可以看代碼,

具體邏輯是這樣的;初始化create()方法中呼叫getBlog()方法,請求博客詳情介面,回傳的博客詳情content通過markdown-it工具進行渲染,

再匯入樣式:

import 'github-markdown.css'

然后在content的div中添加class為markdown-body即可

<template>
    <div>
        <Header></Header>
        <div class="mblog">
            <h2>{{blog.title}}</h2>
            <div>
                <el-link icon="el-icon-edit" v-if="ownBlog">
                    <!--攜帶博客ID跳轉到編輯頁面,對博客進行修改-->
                    <router-link :to="{name:'BlogEdit',params:{blogId: blog.id}}">
                        編輯
                    </router-link>
                </el-link>

                <el-divider direction="vertical"></el-divider>

                <el-link type="danger" @click="messageHandel" v-if="ownBlog">
                        洗掉
                </el-link>
            </div>


            <el-divider></el-divider>
            <div class="markdown-body" v-html="blog.content"></div>
        </div>
        <el-dialog
                :title="title"
                :visible.sync="centerDialogVisible"
                width="30%"
                center>
        <span slot="footer" class="dialog-footer">
            <el-button @click="centerDialogVisible = false">取 消</el-button>
            <el-button type="primary" @click="deleteBlog">確 定</el-button>
        </span>
        </el-dialog>
    </div>
</template>

<script>
    // 匯入所需依賴
    import Header from "../components/Header";
    //為了讓markdown轉換的文本更好看,使用這個檔案進行渲染
    import "github-markdown-css/github-markdown.css"
    import messageModel from "../components/messageModel";

    export default {
        name: "BlogDetail",
        components: {Header, messageModel},
        data() {
            return {
                centerDialogVisible: false,
                title:'您確定要洗掉此條博客嗎?',
                blog: {
                    id: "",
                    title: "",
                    content: ""
                },
                ownBlog: false
            }
        },
        methods:{
            messageHandel(){
                this.centerDialogVisible = true
            },
            deleteBlog(){
                const _this = this
                const blogid = this.$route.params.blogId
                _this.$axios.post("/blog/delete/" + blogid,{
                    headers:{
                        "authorization":localStorage.getItem("token")
                    }
                }).then(res=>{
                    console.log(res)
                    const code = res.data.code
                    if (code === 200){
                        this.centerDialogVisible = false
                        _this.$router.push("/blogs")

                    }else {
                        this.$alert('洗掉失敗!', '提示', {
                            confirmButtonText: '確定',
                        });
                    }
                })
            }
        },
        created() {
            //從路徑中獲取到博客id
            const blogId = this.$route.params.blogId
            console.log(blogId)
            const _this = this
            //按照博客id找到指定的博客,
            _this.$axios.get("/blog/" + blogId).then(res => {
                //獲取到回傳的博客資訊,并且將該資訊回顯給頁面
                const blog = res.data.data;

                _this.blog.id = blog.id
                _this.blog.title = blog.title
                _this.blog.description = blog.description

                /**
                 * 將markdown的文本轉為正常文本
                 * */
                    //拿到markdown渲染資源物件
                var markDownIT = require("markdown-it")
                var md = new markDownIT()   //獲取到markdown-it的物件
                var result = md.render(blog.content)    //將markdown文本渲染成html文本
                _this.blog.content = result //將正常文本賦值給頁面顯示

                //只有在當前的博客的用戶id和當前登錄的用戶的id相符合的時候,才會顯示編輯按鈕
                _this.ownBlog = (blog.userId === _this.$store.getters.getUser.id)

            })
        }
    }

</script>

<style scoped>
    .mblog {
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        width: 100%;
        min-height: 760px;
        margin: 10px 15px;
    }
    /deep/.el-dialog__body{
        padding: 0 !important
    }
</style>

得到的效果如下,其中的博文是我之前在CSDN發布過的,效果基本上一樣,

(8)權限路由攔截

因為我們最開始也提到了部分頁面是需要在登錄的狀態下才能訪問的,那么在前臺應該如何進行攔截呢?其實思路是很簡單的,主要就是給每一個頁面請求添加一個引數,標記其是否是需要在登錄狀態下才能訪問,同時過濾攔截每一個請求,如果該請求是需要登錄才能訪問的,那么就從瀏覽器中獲取token,如果能夠夠獲取到,就放行,否則就跳轉到登錄頁面,

關于這個知識點我單獨總結了一篇博客,歡迎小伙伴們學習!《兩步教你在Vue中設定登錄驗證攔截!》

四、寫在最后&專案總結

到這里整個專案就算是開發完成了,其中也參考了b站大佬的視頻講解,自己也學到到了很多東西,整個專案對學習前后臺分離的新手來說還是非常友好的,其中的shiro+jwt安全驗證、統一結果封裝、以及前臺頁面中路由的設定、存盤和獲取token、跨域解決、登錄驗證等都是非常值得學習的,

專案原始碼我放在gitee了,【原始碼鏈接】,小伙伴們別忘了?star?喲!

一鍵三連加關注!灰小猿帶你上高速啦!???

我是灰小猿,我們下期見!

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/307363.html

標籤:其他

上一篇:H5+echarts模擬全國程式員可視化大資料【附完整原始碼】

下一篇:一文搞懂鎖知識

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more