【做题系统】后端设计

目录

一、设计思路

1.项目背景

2.技术栈选择

二、系统设计

1.系统结构图

2.项目结构 

3.数据建模

4.数据流图

5.主要流程图

三、问题及解决办法

1.实现安全登录、访问

2.数据库中的信息安全问题

3.Mybatis-plus如何实现多表联查问题

4.当查询到很多条数据的时候,如何高效实现分页

5.当删除了一条数据之后,怎么实现恢复

四、亮点

1.采用shiro框架实现安全访问数据库

2.封装了一个自定义的通用结果类来返回请求结果,并且使用了构造器的方法。

3.接口的设计采用了RESTful风格

五、收获与感悟

六、附录

1.接口文档


一、设计思路

1.项目背景

        一种在线做题系统。

2.技术栈选择

        前端: Vue2 + VueX + VueRouter+ Element + HTML + axios

        后端:Spring  + Mybatis-plus + Maven

        数据库:MySQL

        中间件:tomcat + shiro + JWT + log4j

        工具:idea + Vscode + git + navicat + Apifox

        插件:fastjson,druid,lombok,

二、系统设计

1.系统结构图

2.项目结构 

3.数据建模

(1)数据表的信息

sh_user(ID,username,password,salt);
sh_role(ID,rolename,description,locked);
sh_user_role(ID,userId,roleId,enabled);
sh_permission(ID,permissionName,requestPath,description,label,parentId,isDeleted);
sh_role_permission(ID,roleId,permissionId,enabled);
user_info(userId,nikeName,tel,email,quesNum,collectedNum,paperNum,createdTime);
admin_info(userId,nikeName,tel,email);
user_paper(ID,userId,paperId);
paper_info(ID,paperId,paperName,paperSource);
judge_question(quesId,quesType,quesLevel,quesSource,quesContent,quesAnswer,quesAnalysis);
select_question(quesId,quesType,quesLevel,quesSource,quesContent,option1,option2,option3,option4,quesAnswer,quesAnalysis);
discuss_question(quesId,quesType,quesLevel,quesSource,quesContent,quesAnswer,quesAnalysis);
paper_question(ID,paperId,quesId);
question_comment(ID,quesId,userId,content);
question_info(ID,quesId,class);
question_type(ID,quesId,quesType);
user_question_collected(ID,userId,quesId);
user_question_state(ID,userId,quesId,state,yourAnswer,visitTime);

 (2)E-R图

        由于各实体的属性值过多,下面的图中不展示实体的属性值。

 

4.数据流图

(1)管理员

(2)用户

5.主要流程图

(1)注册、登录、退出流程图

(2) 修改个人信息

(3)修改密码

(4)管理员对用户操作

(5)管理员对题目操作

(6)用户组卷

三、问题及解决办法

1.实现安全登录、访问

        这里采用了shiro框架,用Shiro来帮助我们完成:认证、授权、加密、会话管理、与 Web 集成、缓存等操作。具体介绍看我这篇博客

2.数据库中的信息安全问题

        为了防止数据库信息泄露而导致的用户信息泄露问题,这里采用了MD5加密的方式,在数据库中存储加密后的字段。只要方式是通过shiro的配置来完成,在shiro写入的时候使用md5+salt+散列的方式进行数据加密之后存入数据库,读出的时候进行响应的解密。

简单操作步骤:

        ①maven中导入shiro坐标

<!-- shiro -->
<dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>1.9.1</version>
</dependency>

        ② 自定义一个realm,用于认证授权等操作

public class CustomerRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    /**
     * 认证方式
     * @param authenticationToken   校验传入令牌
     * @return AuthenticationInfo
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String principal = (String) authenticationToken.getPrincipal();
        User user = userService.getByUsername(principal);
        if (!ObjectUtils.isEmpty(user)) {
            return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), ByteSource.Util.bytes(user.getSalt()), this.getName());
        }
        return null;
    }

    /**
     * 授权方式
     * @param  principalCollection SimpleAuthenticationInfo对象第一个参数
     * @return AuthorizationInfo
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

}

        ③编写shiro的配置文件

public class ShiroConfig {

    // 1. 创建ShiroFilter,负责拦截请求
    @Bean(name = "shiroFilterFactoryBean")
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置安全管理器
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        // 配置系统受限资源和公告资源
        HashMap<String, String> map = new HashMap<>();
        // anon 表示公共资源, authc 表示需要认证授权
        map.put("/index.html","anon");
        map.put("/page/login.html","anon");     // 放行登录页面
        map.put("/page/register.html","anon");  // 放行注册页面
        map.put("/user/**","anon");          // 放行用户控制器
        map.put("/admin/**","anon");          // 放行管理员控制器

        map.put("/**","authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        // 默认认证界面,失败后跳转
        shiroFilterFactoryBean.setLoginUrl("/page/login.html");

        return shiroFilterFactoryBean;
    }

    //2. 创建安全管理器
    @Bean
    public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 给安全管理器设置realm
        defaultWebSecurityManager.setRealm(realm);

        return defaultWebSecurityManager;
    }

    // 3.创建自定义realm,md5 + salt + 散列
    @Bean
    public Realm getRealm(){
        CustomerRealm customerRealm = new CustomerRealm();
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        //设置使用MD5加密算法
        credentialsMatcher.setHashAlgorithmName("md5");
        //散列次数
        credentialsMatcher.setHashIterations(1024);
        customerRealm.setCredentialsMatcher(credentialsMatcher);
        return customerRealm;
    }
}

3.Mybatis-plus如何实现多表联查问题

        MP提供了大量单表查询的方法,但是没有多表的操作,所以涉及到多表的查询时,需要我们自己实现。

简单步骤如下:

        ①在一个的mapper中创建一个对多的对象,例如,一个用户有很多各角色,一个角色也可以对应很多各用户,那么就可以在用户mapper中定义一个角色列表,在角色mapper中定义一个用户列表。

/**
 * 用户mapper
 */
@Mapper
public interface UserMapper extends BaseMapper<User> {
    /**
     * 获取用户的所有角色
     * @param userId 用户id
     * @return List<Role>
     */
    List<Role> getRoles(String userId);
}

/**
 * 权限mapper
 */
@Mapper
public interface RoleMapper extends BaseMapper<Role> {
    /**
     * 获取一个角色有哪些用户
     * @param roleId  角色id
     * @return List<User>
     */
    List<User> getUsers(String roleId);
}

        ②创建对应的xml文件,在xml文件中实现数据库的查询操作。

<mapper namespace="com.ops.mapper.UserMapper">
    <select id="getRoles" resultType="List" parameterType="String" >
        select roleId from sh_user_role where userId = #{id};
    </select>
</mapper>

4.当查询到很多条数据的时候,如何高效实现分页

        其实Mybatis-plus中已经提供了对应的分类功能,简单实现步骤如下:

①编写拦截器配置类

@Configuration
public class MybatisConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));    //填写对应的数据库
        return interceptor;
    }
}

②在接口处设置分页参数

@GetMapping("/test")
public Response test(){
    Page<Produce> producePage = new Page<>(1,1);
    Page<Produce> page = produceService.page(producePage);
    System.out.println(producePage == page);
    List<Produce> records = page.getRecords();
    for (Produce record : records) {
        System.out.println(record);
    }
    return new Result<>(records, ResultEnum.SUCCESS);
}

5.当删除了一条数据之后,怎么实现恢复

        在表中增加一个字段名为“is_deleted"的字段,字段默认为0,表示没有删除,当执行了删除操作之后,会将该字段设置为1,表示已经删除了,以后的查询操作都会建立在”is_deleted"为0的基础上进行查询。

四、亮点

1.采用shiro框架实现安全访问数据库

        shiro的处理流程为:

                

        流程如下:

        1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息

        2、使用Subject门面获取到封装着用户的数据的标识token

        3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm

        4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法

2.封装了一个自定义的通用结果类来返回请求结果,并且使用了构造器的方法。

        在这里首先定义了一个表示状态的枚举类(status.java),里面封装了所有的状态和状态描述信息。然后再定义了一个结果类,并且使用了构造器的方法。

public class Result {
    private final Integer code;    //状态码
    private final String message;  //错误的的状态信息
    private final Object data;     //数据

    public static class Builder{
        private Integer code;    //状态码
        private String message;  //错误的的状态信息
        private Object data = null;   //数据

        public Builder(Integer code, String message) {
            this.code = code;
            this.message = message;
        }

        public Builder code(Integer code){
            this.code = code;
            return this;
        }
        public Builder message(String message){
            this.message = message;
            return this;
        }
        public Builder data(Object data){
            this.data = data;
            return this;
        }

        public Result build(){
            return new Result(this);
        }

    }

    private Result(Builder builder){
        this.code = builder.code;
        this.message = builder.message;
        this.data = builder.data;
    }

}

        这样就可以通过如下方式来构造:

// 当没有data时:
return new Result.Builder().code(Status.OK().code).message(Status.OK().message);
// 当有data时:
return new Result.Builder().code(Status.OK().code).message(Status.OK().message).data(data);

        关于这一部分的详细知识点可以看我的另一篇博客(实在是太优雅啦!!)

3.接口的设计采用了RESTful风格

         具体介绍可以看我这一篇博客。接口文档见附录。

五、收获与感悟

        通过这次后端项目的实战,加深了对框架的理解和认识,熟悉了利用框架开发的流程和方法;同时还学会了不同技术和spring-boot进行整合的方式;接触到了mybatis-plus,它是mybatis的升级版,是由国人开发的,符合了中国人的开发思维,能够极大地简化开发;同时新学习了shiro安全框架,保证数据和程序运行的安全性,相比于spring-security框架,shiro更加简洁轻便,学习成本更低。

        当然这个项目还有很多不完善的地方,在异常报错的处理方面还不够细致,当出现异常的时候,不能够准确地指出问题的出处,而是比较笼统地指出问题的原因,这一点需要改善,可以通过在status类中新增各种的请求状态来完成;同时,项目中没有很注重失败原子性的操作,失败的方法调用应该使对象保持在被调用之前的状态,当然实现的方法也有很多,可以看我的这篇博客;当然,由于项目的规模是比较小的,所以也没有注重高并发相关的处理。

六、附录

1.接口文档

        由于篇幅限制,接口文档点击这里查看

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值