spring boot 2 + shiro 實現許可權管理

来源:https://www.cnblogs.com/gdjlc/archive/2019/12/17/12057612.html
-Advertisement-
Play Games

Shiro是一個功能強大且易於使用的Java安全框架,主要功能有身份驗證、授權、加密和會話管理。 ...


Shiro是一個功能強大且易於使用的Java安全框架,主要功能有身份驗證、授權、加密和會話管理。
看了網上一些文章,下麵2篇文章寫得不錯。
Springboot2.0 集成shiro許可權管理 
Spring Boot:整合Shiro許可權框架 

自己動手敲了下代碼,在第一篇文章上加入了第二篇文章的Swagger測試,另外自己加入lombok簡化實體類代碼,一些地方代碼也稍微修改了下,過程中也碰到一些問題,最終代碼成功運行。

開發版本:
IntelliJ IDEA 2019.2.2
jdk1.8
Spring Boot 2.1.11
MySQL8.0

一、創建SpringBoot項目,添加依賴包和配置application.yml

在IDEA中創建一個新的SpringBoot項目

1、pom.xml引用的依賴包如下:

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

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.2</version>
        </dependency>

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.10</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

2、application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update #指定為update,每次啟動項目檢測表結構有變化的時候會新增欄位,表不存在時會新建,如果指定create,則每次啟動項目都會清空數據並刪除表,再新建
      naming:
        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl #按欄位名字建表
        #implicit-strategy: org.hibernate.boot.model.naming.ImplicitNamingStrategyLegacyJpaImpl #駝峰自動映射為下劃線格式
    show-sql: true # 預設false,在日誌里顯示執行的sql語句
    database: mysql
    database-platform: org.hibernate.dialect.MySQL5InnoDBDialect

二、創建實體類

創建User、Role、Permission三個實體類,根據規則會自動生成兩個中間表,最終資料庫有5個表。
另外添加一個model處理登錄結果。

1、User

package com.example.shiro.entity;

import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Entity
@Getter
@Setter
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long userId;

    @Column(nullable = false, unique = true)
    private String userName; //登錄用戶名

    @Column(nullable = false)
    private String name;//名稱(昵稱或者真實姓名,根據實際情況定義)

    @Column(nullable = false)
    private String password;

    private String salt;//加密密碼的鹽

    private byte state;//用戶狀態,0:創建未認證(比如沒有激活,沒有輸入驗證碼等等)--等待驗證的用戶 , 1:正常狀態,2:用戶被鎖定.

    @ManyToMany(fetch= FetchType.EAGER)//立即從資料庫中進行載入數據;
    @JoinTable(name = "UserRole", joinColumns = { @JoinColumn(name = "userId") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<Role> roleList;// 一個用戶具有多個角色

    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm")
    private LocalDateTime createTime;//創建時間

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate expiredDate;//過期日期

    private String email;

    /**密碼鹽. 重新對鹽重新進行了定義,用戶名+salt,這樣就不容易被破解 */
    public String getCredentialsSalt(){
        return this.userName+this.salt;
    }
}

說明:
這裡使用@Getter,@Setter註解,不能使用@Data註解,因為實體使用了jpa的@oneToMany ,載入方式為lazy,在主表查詢時關聯表未載入,而主表使用@Data後會實現帶關聯表屬性的hashCode和equals等方法。在運行過程中調用關聯表數據時會顯示異常 java.lang.stackoverflowerror。

2、Role

package com.example.shiro.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;

@Entity
@Getter
@Setter
public class Role {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long roleId; // 編號

    @Column(nullable = false, unique = true)
    private String role; // 角色標識程式中判斷使用,如"admin",這個是唯一的:

    private String description; // 角色描述,UI界面顯示使用

    private Boolean available = Boolean.TRUE; // 是否可用,如果不可用將不會添加給用戶

    //角色 -- 許可權關係:多對多關係;
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="RolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})
    private List<Permission> permissions;

    // 用戶 - 角色關係定義;
    @ManyToMany
    @JoinTable(name="UserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="userId")})
    private List<User> users;// 一個角色對應多個用戶
}

3、Permission

package com.example.shiro.entity;

import lombok.Getter;
import lombok.Setter;

import javax.persistence.*;
import java.util.List;

@Entity
@Getter
@Setter
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long permissionId;//主鍵.

    @Column(nullable = false)
    private String permissionName;//名稱.

    @Column(columnDefinition="enum('menu','button')")
    private String resourceType;//資源類型,[menu|button]

    private String url;//資源路徑.

    private String permission; //許可權字元串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view

    private Long parentId; //父編號

    private String parentIds; //父編號列表

    private Boolean available = Boolean.TRUE;

    //角色 -- 許可權關係:多對多關係;
    @ManyToMany(fetch= FetchType.EAGER)
    @JoinTable(name="RolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")})
    private List<Role> roles;
}

4、LoginResult

package com.example.shiro.model;

import lombok.Data;

@Data
public class LoginResult {
    private boolean isLogin = false;
    private String result;
}

三、DAO

1、添加一個DAO基礎介面:BaseRepository

package com.example.shiro.repository;

import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;

import java.io.Serializable;

@NoRepositoryBean
public interface BaseRepository<T, I extends Serializable> extends PagingAndSortingRepository<T, I>, JpaSpecificationExecutor<T> {
}

2、UserRepository

package com.example.shiro.repository;

import com.example.shiro.entity.User;

public interface UserRepository extends BaseRepository<User,Long> {
    User findByUserName(String userName);
}

四、Service

1、LoginService

package com.example.shiro.service;

import com.example.shiro.model.LoginResult;

public interface LoginService {

    LoginResult login(String userName, String password);

    void logout();
}

2、UserService

package com.example.shiro.service;

import com.example.shiro.entity.User;

public interface UserService {
    User findByUserName(String userName);
}

五、Service.impl

1、LoginServiceImpl

package com.example.shiro.service.impl;

import com.example.shiro.model.LoginResult;
import com.example.shiro.repository.UserRepository;
import com.example.shiro.service.LoginService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Service;

@Service
public class LoginServiceImpl implements LoginService {

    @Override
    public LoginResult login(String userName, String password) {
        LoginResult loginResult = new LoginResult();
        if (userName == null || userName.isEmpty()) {
            loginResult.setLogin(false);
            loginResult.setResult("用戶名為空");
            return loginResult;
        }
        String msg = "";
        // 1、獲取Subject實例對象
        Subject currentUser = SecurityUtils.getSubject();

//        // 2、判斷當前用戶是否登錄
//        if (currentUser.isAuthenticated() == false) {
//
//        }

        // 3、將用戶名和密碼封裝到UsernamePasswordToken
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);

        // 4、認證
        try {
            currentUser.login(token);// 傳到MyAuthorizingRealm類中的方法進行認證
            Session session = currentUser.getSession();
            session.setAttribute("userName", userName);
            loginResult.setLogin(true);
            return loginResult;
            //return "/index";
        } catch (UnknownAccountException e) {
            e.printStackTrace();
            msg = "UnknownAccountException -- > 賬號不存在:";
        } catch (IncorrectCredentialsException e) {
            msg = "IncorrectCredentialsException -- > 密碼不正確:";
        } catch (AuthenticationException e) {
            e.printStackTrace();
            msg = "用戶驗證失敗";
        }

        loginResult.setLogin(false);
        loginResult.setResult(msg);

        return loginResult;
    }

    @Override
    public void logout() {
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
    }
}

2、UserServiceImpl

package com.example.shiro.service.impl;

import com.example.shiro.entity.User;
import com.example.shiro.repository.UserRepository;
import com.example.shiro.service.UserService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class UserServiceImpl implements UserService {
    @Resource
    private UserRepository userRepository;
    @Override
    public User findByUserName(String userName) {
        return userRepository.findByUserName(userName);
    }
}

六、config配置類

1、創建Realm

package com.example.shiro.config;

import com.example.shiro.entity.Permission;
import com.example.shiro.entity.Role;
import com.example.shiro.entity.User;
import com.example.shiro.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import javax.annotation.Resource;

public class MyShiroRealm extends AuthorizingRealm {
    @Resource
    private UserService userService;

    /**
     * 身份認證:驗證用戶輸入的賬號和密碼是否正確。
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //獲取用戶輸入的賬號
        String userName = (String) token.getPrincipal();
        //通過username從資料庫中查找 User對象.
        //實際項目中,這裡可以根據實際情況做緩存,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重覆執行該方法
        User user = userService.findByUserName(userName);
        if (user == null) {
            return null;
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user,//這裡傳入的是user對象,比對的是用戶名,直接傳入用戶名也沒錯,但是在授權部分就需要自己重新從資料庫里取許可權
                user.getPassword(),//密碼
                ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
                getName()//realm name
        );
        return authenticationInfo;
    }

    /**
     * 許可權信息
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        //如果身份認證的時候沒有傳入User對象,這裡只能取到userName
        //也就是SimpleAuthenticationInfo構造的時候第一個參數傳遞需要User對象
        User user  = (User)principals.getPrimaryPrincipal();
        for(Role role : user.getRoleList()){
            //添加角色
            authorizationInfo.addRole(role.getRole());
            for(Permission p:role.getPermissions()){
                //添加許可權
                authorizationInfo.addStringPermission(p.getPermission());
            }
        }
        return authorizationInfo;
    }

}

2、配置Shiro

package com.example.shiro.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

@Configuration
public class ShiroConfig {
    //將自己的驗證方式加入容器
    @Bean
    MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return myShiroRealm;
    }

    //許可權管理,配置主要是Realm的管理認證
    @Bean
    DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myShiroRealm());
        return manager;
    }

    //憑證匹配器(密碼校驗交給Shiro的SimpleAuthenticationInfo進行處理)
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列演算法:這裡使用MD5演算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次數,比如散列兩次,相當於 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    // Filter工廠,設置對應的過濾條件和跳轉條件
    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager());
        Map<String, String> filterMap = new HashMap<String, String>();
        // 登出
        filterMap.put("/logout", "logout");
        // swagger
        filterMap.put("/swagger**/**", "anon");
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/v2/**", "anon");
        // 對所有用戶認證
        filterMap.put("/**", "authc");
        // 登錄
        bean.setLoginUrl("/login");
        // 首頁
        bean.setSuccessUrl("/index");
        // 未授權頁面,認證不通過跳轉
        bean.setUnauthorizedUrl("/403");
        bean.setFilterChainDefinitionMap(filterMap);      
        return bean;
    }

    //開啟shiro aop註解支持.
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

    //shiro註解模式下,登錄失敗或者是沒有許可權都是拋出異常,並且預設的沒有對異常做處理,配置一個異常處理
    @Bean(name="simpleMappingExceptionResolver")
    public SimpleMappingExceptionResolver
    createSimpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("DatabaseException", "databaseError");//資料庫異常處理
        mappings.setProperty("UnauthorizedException","/403");
        r.setExceptionMappings(mappings);  // None by default
        r.setDefaultErrorView("error");    // No default
        r.setExceptionAttribute("exception");     // Default is "exception"
        return r;
    }
}

3、配置swagger

package com.example.shiro.config;

import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.any())
                .paths(PathSelectors.any()).build();
    }
    private static ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("API文檔")
                .description("Swagger API 文檔")
                .version("1.0")
                .contact(new Contact("name..", "url..", "email.."))
                .build();
    }
}

七、controller

1、LoginController用來處理登錄

package com.example.shiro.controller;

import com.example.shiro.entity.User;
import com.example.shiro.model.LoginResult;
import com.example.shiro.service.LoginService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

@RestController
public class LoginController {
    @Resource
    private LoginService loginService;

    @GetMapping(value = "/login")
    public String login() {
        return "登錄頁";
    }

    @PostMapping(value = "/login")
    public String login(@RequestBody User user) {
        System.out.println("login()");
        String userName = user.getUserName();
        String password = user.getPassword();
        LoginResult loginResult = loginService.login(userName,password);
        if(loginResult.isLogin()){
            return "登錄成功";
        } else {
            return "登錄失敗:" + loginResult.getResult();
        }
    }

    @GetMapping(value = "/index")
    public String index() {
        return "主頁";
    }

    @GetMapping(value = "/logout")
    public String logout() {
        return "退出";
    }

    @GetMapping("/403")
    public String unauthorizedRole(){
        return "沒有許可權";
    }
}

2、UserController用來測試訪問,許可權全部採用註解的方式。

package com.example.shiro.controller;

import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/user")
public class UserController {
    //用戶查詢
    @GetMapping("/userList")
    @RequiresPermissions("user:view")//許可權管理;
    public String userInfo(){
        return "userList";
    }

    //用戶添加
    @GetMapping("/userAdd")
    @RequiresPermissions("user:add")//許可權管理;
    public String userInfoAdd(){
        return "userAdd";
    }

    //用戶刪除
    @GetMapping("/userDel")
    @RequiresPermissions("user:del")//許可權管理;
    public String userDel(){
        return "userDel";
    }
}

八、資料庫預設一些數據

先運行一遍程式,JPA生成資料庫表後,手工執行sql腳本插入樣本數據。
用戶admin的原始密碼是123456。

INSERT INTO `user` (`userId`,`username`,`name`,`password`,`salt`,`state`)
VALUES ('1', 'admin', '管理員', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 1);

INSERT INTO `permission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)
VALUES (1,1,'用戶管理',0,'0/','user:view','menu','user/userList');
INSERT INTO `permission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)
VALUES (2,1,'用戶添加',1,'0/1','user:add','button','user/userAdd');
INSERT INTO `permission` (`permissionId`,`available`,`permissionname`,`parentid`,`parentids`,`permission`,`resourcetype`,`url`)
VALUES (3,1,'用戶刪除',1,'0/1','user:del','button','user/userDel');

INSERT INTO `role` (`roleid`,`available`,`description`,`role`) VALUES (1,1,'管理員','admin');

INSERT INTO `rolepermission` (`permissionid`,`roleid`) VALUES (1,1);
INSERT INTO `rolepermission` (`permissionid`,`roleid`) VALUES (2,1);

INSERT INTO `userrole` (`roleid`,`userId`) VALUES (1,1);

九、swagger測試

 1、啟動項目,訪問http://localhost:8080/swagger-ui.html

  2、訪問/user/userAdd, Response body顯示登錄頁

 3、訪問POST的/login,請求參數輸入:

{
"userName": "admin",
"password": "123456"
}

 Response body顯示登錄成功。

 4、再次訪問/user/userAdd,因為登錄成功了並且有許可權,這次Response body顯示userAdd

 

 5、訪問/user/userDel,因為資料庫沒有配置許可權,所以Response body顯示沒有許可權

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 項目介紹 項目中需要用到下拉樹多選功能,找到兩個相關組件moretop-layui-select-ext和wujiawei0926-treeselect,但是moretop-layui-select-ext不支持樹結構,wujiawei0926-treeselect不支持多選,於是乾脆仿照moret ...
  • JavaScript 錯誤 - Throw 和 Try to Catch try 語句使您能夠測試代碼塊中的錯誤。 catch 語句允許您處理錯誤。 throw 語句允許您創建自定義錯誤。 finally 使您能夠執行代碼,在 try 和 catch 之後,無論結果如何。 錯誤總會發生! 當執行 J ...
  • 本書內容 本書從書名就可以看出來,講了架構的兩個東西,一個是原理,一個是案例。 案例部分沒有在導圖中體現,不過建議讀者還是要看一下案例,能夠通過案例對原理有更加深刻的印象 推薦程度 4.5 顆星 推薦原因 通讀本書,能對大型網站有更加直觀的感受 細節之處,能夠指導你設計網站架構選用的具體方案 即使以 ...
  • 在k8s里,你可以通過服務名去訪問相同namespace里的服務,然後服務可以解析到對應的pod,從而再由pod轉到對應的容器里,我們可以認為這個過程有兩個port的概念,service port 就是服務的port,在k8s配置文件里用 表示,還有一個是pod和容器的port,用targetPor ...
  • PHP 7.4.0 發佈了,此版本標志著 PHP 7 系列的第四次特性更新。 看了英文手冊後,發現其進行了許多改進,並帶來了一些新特性,現在將這些新特性您: 1.Typed Properties 類型屬性 類屬性現在支持類型聲明,以下示例將強制 $User-> id 只能分配 int 值,而 $Us ...
  • 首先介紹一下Java的各個層級,先放一張圖: 硬體,操作系統和操作系統介面:這三級不說大家都知道,操作系統有很多種,比如Windows,Linux。Windows又分為win7,win10,win xp等等;Linux有Ubuntu,CentOS;操作系統介面就是系統為開發者預留的,方便調用從而控制 ...
  • 之前的aop是通過手動創建代理類來進行通知的,但是在日常開發中,我們並不願意在代碼中硬編碼這些代理類,我們更願意使用DI和IOC來管理aop代理類。Spring為我們提供了以下方式來使用aop框架 一、以聲明的方式配置AOP(就是使用xml配置文件) 1.使用ProxyFactoryBean的方式: ...
  • tcp傳輸的數據是以流的形式傳輸的,因此就沒有辦法判斷到哪裡結束算是自己的一個消息,這樣就會出現粘包問題,多個包粘在一起了 可以使用這樣一個自定義的形式來解決,一個消息分為 head+body head包括數據的長度和數據編號 , 長度和編號都是uint32類型 也就是32位 占有4個位元組 , 總共 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...