Merge remote-tracking branch 'origin/书籍中心zgj' into 书籍中心zgj

书籍中心zgj
zgj 8 months ago
commit 4540fb8d50

@ -10,26 +10,52 @@ import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.support.config.FastJsonConfig; import com.alibaba.fastjson.support.config.FastJsonConfig;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter; import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
// @SpringBootApplication是一个组合注解它整合了多个Spring Boot相关的注解包括@Configuration表明这个类是一个配置类可用于定义Bean等配置信息
// @EnableAutoConfiguration开启Spring Boot的自动配置功能会根据项目依赖等情况自动配置很多默认的组件和行为、@ComponentScan用于扫描指定包及其子包下的所有Spring组件如@Component、@Service、@Controller等注解标记的类使得这些类能够被Spring容器管理
// 这个注解标记在类上表示这是一个Spring Boot的主应用类启动类所在的包及其子包下的相关组件会被自动扫描并纳入Spring容器管理同时开启自动配置等功能整个Spring Boot应用从这个类开始启动。
@SpringBootApplication @SpringBootApplication
public class Application { public class Application {
// 应用程序的入口方法是Java应用程序开始执行的地方在这个方法中通过SpringApplicationBuilder来构建并启动Spring Boot应用传入当前类Application.class作为参数
// 告诉Spring Boot框架这个类是应用的主配置类同时将命令行参数args传递给它用于处理一些启动时可配置的相关信息例如配置不同的运行环境等情况具体取决于参数如何定义和使用启动整个Spring Boot应用初始化Spring容器、加载配置、启动相关服务等操作都会在这里开始执行。
public static void main(String[] args) { public static void main(String[] args) {
new SpringApplicationBuilder(Application.class).run(args); new SpringApplicationBuilder(Application.class).run(args);
} }
/** /**
* FastJsonJackson * FastJsonJackson
* @return * SpringBeanFastJsonSpring BootHTTPJavaJSONJSONJava
* JSONJacksonFastJsonJSONFastJsonHttpMessageConverterHttpMessageConvertersSpring Boot使
*
* @return HttpMessageConvertersFastJsonHttpMessageConverterSpringHTTPJSON
*/ */
@Bean @Bean
public HttpMessageConverters fastJsonHttpMessageConverters() { public HttpMessageConverters fastJsonHttpMessageConverters() {
// 创建一个FastJsonHttpMessageConverter对象它是Spring框架中用于处理HTTP消息转换并且基于FastJson库来实现JSON数据序列化和反序列化的一个转换器
// 后续会对它进行配置设置FastJson相关的参数使其按照应用的需求来正确地处理JSON数据比如设置日期格式、避免循环引用等序列化特性。
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 创建一个FastJsonConfig对象用于配置FastJson的各种参数和特性通过这个对象可以设置如日期格式、序列化相关的特性选项等内容以定制FastJson在处理JSON数据时的行为满足应用在不同场景下对JSON序列化和反序列化的要求。
FastJsonConfig fastJsonConfig = new FastJsonConfig(); FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 设置FastJson在序列化日期类型数据时的格式将日期格式设置为"yyyy-MM-dd HH:mm:ss"这样在将Java中的日期对象转换为JSON字符串时会按照这个格式进行格式化输出便于前端或其他客户端按照统一的格式进行解析和展示日期数据。
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
// 设置FastJson的序列化特性这里使用SerializerFeature.DisableCircularReferenceDetect特性它的作用是禁用循环引用检测
// 当Java对象之间存在复杂的关联关系比如互相引用避免在序列化过程中出现循环引用导致的问题如栈溢出等异常情况同时也能确保序列化的JSON数据结构更符合预期不会出现一些由于循环引用导致的不合理结构按照设定的规则进行序列化处理。
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
// 将配置好的FastJsonConfig对象设置到FastJsonHttpMessageConverter中使得FastJsonHttpMessageConverter在进行JSON数据的序列化和反序列化操作时会按照FastJsonConfig中设定的参数和特性来执行
// 这样就完成了对FastJson在Spring Boot应用中处理HTTP消息转换时的相关配置工作使其能够按照应用需求来正确地转换JSON数据。
fastConverter.setFastJsonConfig(fastJsonConfig); fastConverter.setFastJsonConfig(fastJsonConfig);
// 将配置好的FastJsonHttpMessageConverter对象赋值给converter变量这里其实可以直接使用fastConverter这个赋值操作略显多余但可能是出于代码逻辑清晰性或者后续可能有其他修改的考虑等情况
// 后续会将这个配置好的转换器封装在HttpMessageConverters对象中返回给Spring容器管理并使用。
FastJsonHttpMessageConverter converter = fastConverter; FastJsonHttpMessageConverter converter = fastConverter;
// 创建一个HttpMessageConverters对象并将配置好的FastJsonHttpMessageConverter作为参数传入构造函数将其封装在HttpMessageConverters中
// HttpMessageConverters是Spring框架用于管理多个HTTP消息转换器的一个类在这里将FastJson相关的转换器添加进去后Spring Boot应用在处理HTTP消息转换时就会使用这个配置好的基于FastJson的转换器来处理JSON数据实现了用FastJson替代默认的JSON处理框架如Jackson的功能
// 最后返回这个HttpMessageConverters对象供Spring容器管理使用使得在整个Spring Boot应用中涉及HTTP请求和响应的JSON数据处理都按照配置好的FastJson相关特性来执行。
return new HttpMessageConverters(converter); return new HttpMessageConverters(converter);
} }
}
}

@ -5,19 +5,41 @@ import org.apache.ibatis.reflection.MetaObject;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** // 以下这行被注释掉的@Component注解如果取消注释Spring会将这个类作为一个组件扫描并注册到Spring容器中
* , // 这样就可以自动应用它所实现的功能在这里就是公共字段自动填充功能不过当前代码中只是定义了类的结构和基本方法可能并不想让Spring直接管理它所以暂时注释掉了。
*/
//@Component //@Component
// 这个类继承自MetaObjectHandler类在MyBatis Plus框架中MetaObjectHandler用于实现对实体对象的公共字段进行自动填充的功能
// 比如在插入或更新数据库记录时,自动设置创建时间、更新时间、创建人、更新人等公共字段的值,通过继承该类并重写相应的方法,
// 可以自定义这些公共字段的填充逻辑以满足具体业务需求。这里定义的MyMetaObjectHandler类就是用于此目的不过当前代码中尚未实现具体的填充逻辑只是搭建了基本的方法框架。
public class MyMetaObjectHandler extends MetaObjectHandler { public class MyMetaObjectHandler extends MetaObjectHandler {
// 创建一个静态的日志记录器对象使用SLF4J框架的LoggerFactory来获取与当前类MyMetaObjectHandler对应的Logger实例
// 通过这个日志记录器可以在代码中方便地记录各种级别的日志信息如DEBUG、INFO、WARN、ERROR等便于在实现公共字段自动填充逻辑过程中进行调试、监控以及问题排查等操作。
protected final static Logger logger = LoggerFactory.getLogger(MyMetaObjectHandler.class); protected final static Logger logger = LoggerFactory.getLogger(MyMetaObjectHandler.class);
/**
* MetaObjectHandlerinsertFillMyBatis Plus
*
* MetaObject
* 使Java
*
* @param metaObject MetaObjectMyBatis
* 便
*/
@Override @Override
public void insertFill(MetaObject metaObject) { public void insertFill(MetaObject metaObject) {
} }
/**
* MetaObjectHandlerupdateFillMyBatis Plus
*
* 使MetaObject
* 使
*
* @param metaObject MetaObject
*
*/
@Override @Override
public void updateFill(MetaObject metaObject) { public void updateFill(MetaObject metaObject) {
} }
} }

@ -19,40 +19,85 @@ import com.baomidou.mybatisplus.plugins.parser.tenant.TenantSqlParser;
import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.LongValue;
// @Configuration注解表明这个类是一个Spring的配置类用于定义各种Bean以及相关的配置信息Spring容器会在启动时读取这个类中的配置
// 将其中定义的Bean创建并管理起来供其他组件进行依赖注入和使用从而实现整个应用的各种功能配置。
@Configuration @Configuration
// @MapperScan注解用于指定MyBatis Plus的Mapper接口所在的包路径它会自动扫描该路径下的所有Mapper接口并创建对应的代理实现类
// 使得这些Mapper接口能够与数据库进行交互这里指定扫描"com.tamguo.modules.*.dao*"路径下的Mapper接口方便对不同模块下的数据库操作进行统一管理。
@MapperScan("com.tamguo.modules.*.dao*") @MapperScan("com.tamguo.modules.*.dao*")
public class MybatisPlusConfig { public class MybatisPlusConfig {
/**
* PerformanceInterceptorSpringBean
* PerformanceInterceptorMyBatis PlusSQL
* SQL便
*
* @return PerformanceInterceptorSpringMyBatis PlusSQL
*/
@Bean @Bean
public PerformanceInterceptor performanceInterceptor() { public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor(); return new PerformanceInterceptor();
} }
/** /**
* mybatis-plus<br> * PaginationInterceptorSpringBeanMyBatis Plus
* http://mp.baomidou.com<br> *
* @return PaginationInterceptorMyBatis PlusSQL
*/ */
@Bean @Bean
public PaginationInterceptor paginationInterceptor() { public PaginationInterceptor paginationInterceptor() {
// 创建一个PaginationInterceptor实例它是MyBatis Plus中用于实现分页功能的核心拦截器
// 通过拦截SQL语句自动添加分页相关的语法如 LIMIT 等,具体取决于数据库类型),实现对查询结果的分页处理。
PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
// 设置开启PageHelper的支持这里的PageHelper可能是指与其他分页插件兼容的一种模式或者相关功能具体取决于项目中对分页的整体应用情况
// 通过设置为true使得PaginationInterceptor能够更好地与可能存在的PageHelper相关功能协同工作确保分页功能的正常实现。
paginationInterceptor.setLocalPage(true);
/* /*
* SQL <br> * SQL <br>
* 1 cookie SQL <br> * 1 cookie SQL <br>
* SQLSQLSQL
*/ */
List<ISqlParser> sqlParserList = new ArrayList<>(); List<ISqlParser> sqlParserList = new ArrayList<>();
// 创建一个TenantSqlParser实例它是基于MyBatis Plus的SQL解析器专门用于处理多租户场景下的SQL语句解析和修改
// 通过与TenantHandler配合能够根据租户信息动态地在SQL中添加相应的条件确保每个租户只能访问自己的数据。
TenantSqlParser tenantSqlParser = new TenantSqlParser(); TenantSqlParser tenantSqlParser = new TenantSqlParser();
// 为TenantSqlParser设置TenantHandlerTenantHandler用于定义获取租户ID、租户ID对应的数据库列名以及判断是否对某个表进行租户过滤等逻辑
// 在这里通过匿名内部类的方式实现了TenantHandler接口具体配置了多租户相关的核心逻辑。
tenantSqlParser.setTenantHandler(new TenantHandler() { tenantSqlParser.setTenantHandler(new TenantHandler() {
/**
* IDLongValueID1L
* CookieID1
*
* @return IDSQLID
*/
@Override @Override
public Expression getTenantId() { public Expression getTenantId() {
return new LongValue(1L); return new LongValue(1L);
} }
/**
* ID"course_id"SQL
* "WHERE course_id = 1"1getTenantIdID
*
* @return IDSQL
*/
@Override @Override
public String getTenantIdColumn() { public String getTenantIdColumn() {
return "course_id"; return "course_id";
} }
/**
* true
* truefalse
*
*
* @param tableName
* @return truefalse访
*/
@Override @Override
public boolean doTableFilter(String tableName) { public boolean doTableFilter(String tableName) {
// 这里可以判断是否过滤表 // 这里可以判断是否过滤表
@ -60,9 +105,14 @@ public class MybatisPlusConfig {
} }
}); });
// 将配置好的TenantSqlParser添加到sqlParserList中这个列表用于存储所有需要应用的SQL解析器
// 后续会将这个列表设置到PaginationInterceptor中使得在处理分页相关的SQL语句时同时也能应用这些解析器进行多租户相关的SQL修改等操作。
sqlParserList.add(tenantSqlParser); sqlParserList.add(tenantSqlParser);
// 将包含TenantSqlParser的sqlParserList设置到PaginationInterceptor中这样在执行SQL语句时
// PaginationInterceptor不仅会处理分页逻辑还会调用列表中的各个SQL解析器对SQL进行额外的解析和修改如添加多租户筛选条件等
paginationInterceptor.setSqlParserList(sqlParserList); paginationInterceptor.setSqlParserList(sqlParserList);
// 以下过滤方式与 @SqlParser(filter = true) 注解等效 // 以下过滤方式与 @SqlParser(filter = true) 注解等效
// paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() { // paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
// @Override // @Override
@ -75,19 +125,31 @@ public class MybatisPlusConfig {
// return false; // return false;
// } // }
// }); // });
return paginationInterceptor; return paginationInterceptor;
} }
/**
* MetaObjectHandlerSpringBean
* MetaObjectHandlerMyBatis Plus
* 便
*
* @return MetaObjectHandlerMyMetaObjectHandlerMetaObjectHandler
* SpringMyBatis Plus
*/
@Bean @Bean
public MetaObjectHandler metaObjectHandler(){ public MetaObjectHandler metaObjectHandler() {
return new MyMetaObjectHandler(); return new MyMetaObjectHandler();
} }
/** /**
* sql * ISqlInjectorSpringBean使LogicSqlInjector
* MyBatis PlusSqlInjectorSQL
*
* @return LogicSqlInjectorMyBatis PlusSQL使SQL
*/ */
@Bean @Bean
public ISqlInjector sqlInjector(){ public ISqlInjector sqlInjector() {
return new LogicSqlInjector(); return new LogicSqlInjector();
} }
} }

@ -18,55 +18,95 @@ import org.springframework.beans.factory.annotation.Autowired;
import com.tamguo.modules.member.model.MemberEntity; import com.tamguo.modules.member.model.MemberEntity;
import com.tamguo.modules.member.service.IMemberService; import com.tamguo.modules.member.service.IMemberService;
/** // 该类继承自AuthorizingRealm类在Apache Shiro框架中AuthorizingRealm是一个用于实现认证authentication和授权authorization功能的抽象基类
* // 自定义的Realm需要继承它并实现相关抽象方法来定义具体的认证和授权逻辑这里的MemberRealm类就是针对会员Member相关业务定制的Realm用于处理会员的认证和授权操作。
*
*/
public class MemberRealm extends AuthorizingRealm { public class MemberRealm extends AuthorizingRealm {
// 通过@Autowired注解自动注入IMemberService接口的实现类实例IMemberService用于处理与会员相关的业务逻辑
// 例如根据用户名查询会员信息、获取会员登录失败次数、更新会员登录失败次数以及更新会员最后登录时间等操作,
// 在本类的认证和授权逻辑中,需要借助这些业务方法来获取会员相关数据以及更新会员状态等信息。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/** /**
* () * Shiro访
*/ * AuthorizingRealm
* SimpleAuthorizationInfo
* PrincipalCollectionSimpleAuthorizationInfo便Shiro
*
* @param principals PrincipalCollection
*
* @return AuthorizationInfoSimpleAuthorizationInfoShiro使
*/
@Override @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Set<String > permsSet = null; // 初始化一个用于存储权限字符串的集合变量当前设置为null实际应用中应该从数据库等数据源查询出当前用户所拥有的权限字符串并添加到这个集合中
// 权限字符串通常是一些具有特定格式的字符串,用于标识不同的操作权限(比如"user:add"表示用户添加权限,"order:delete"表示订单删除权限等,具体格式根据业务设计而定)。
Set<String> permsSet = null;
// 创建一个SimpleAuthorizationInfo对象它是Shiro框架中用于表示用户授权信息的一个简单实现类通过调用其相关方法可以设置用户的角色、权限等信息。
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 将当前为空的权限集合设置到SimpleAuthorizationInfo对象中实际业务中应该在此处添加从数据库查询到的用户实际拥有的权限
// 使得Shiro框架后续能基于这些权限信息来判断用户是否有权限访问特定资源。
info.setStringPermissions(permsSet); info.setStringPermissions(permsSet);
return info; return info;
} }
/** /**
* () * Shiro
* AuthorizingRealm
*
* @param token AuthenticationToken使Token
*
* @return AuthenticationInfoSimpleAuthenticationInfo
* Shiro便使
* @throws AuthenticationException AuthenticationException
* Shiro
*/ */
@Override @Override
protected AuthenticationInfo doGetAuthenticationInfo( protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException { AuthenticationToken token) throws AuthenticationException {
// 从AuthenticationToken对象中获取用户输入的用户名信息由于在通常情况下Shiro默认的Token实现类会将用户名存储在Principal位置所以通过强制类型转换获取用户名的字符串表示形式。
String username = (String) token.getPrincipal(); String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 从AuthenticationToken对象中获取用户输入的密码信息Shiro默认的Token实现类会将密码以字符数组的形式存储在Credentials位置这里通过将字符数组转换为字符串来获取密码内容。
MemberEntity member = iMemberService.findByUsername(username); String password = new String((char[]) token.getCredentials());
if(member == null) {
throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码"); // 调用iMemberService的findByUsername方法根据获取到的用户名去数据库等数据源中查询对应的会员实体对象用于后续验证密码以及判断账号状态等操作。
} MemberEntity member = iMemberService.findByUsername(username);
Integer loginFailureCount = iMemberService.getLoginFailureCount(member);
if(loginFailureCount > 10) { // 如果查询到的会员对象为null说明用户名不存在抛出UnknownAccountException异常Shiro框架会捕获这个异常并根据配置向客户端返回相应的错误提示信息如提示用户名或密码有误等
throw new LockedAccountException("账号被锁定"); if (member == null) {
} throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码");
}
if(!new Sha256Hash(password).toHex().equals(member.getPassword())){
// 调用iMemberService的getLoginFailureCount方法获取当前会员的登录失败次数用于判断账号是否因为多次登录失败而被锁定。
Integer loginFailureCount = iMemberService.getLoginFailureCount(member);
// 如果登录失败次数大于10次说明账号存在异常登录情况可能被恶意攻击等为了保证账号安全抛出LockedAccountException异常Shiro框架会处理这个异常并告知客户端账号被锁定禁止登录。
if (loginFailureCount > 10) {
throw new LockedAccountException("账号被锁定");
}
// 使用Sha256Hash对用户输入的密码进行哈希处理将密码转换为不可逆的哈希值形式并与数据库中存储的会员密码通常也是经过哈希处理后存储的进行比对
// 如果两者不一致说明用户输入的密码错误此时增加登录失败次数调用iMemberService的updateLoginFailureCount方法将更新后的登录失败次数保存到数据库中
// 然后抛出IncorrectCredentialsException异常Shiro框架会根据此异常向客户端返回密码错误的提示信息要求用户重新输入密码。
if (!new Sha256Hash(password).toHex().equals(member.getPassword())) {
loginFailureCount++; loginFailureCount++;
iMemberService.updateLoginFailureCount(member , loginFailureCount); iMemberService.updateLoginFailureCount(member, loginFailureCount);
throw new IncorrectCredentialsException("用户名或密码有误,请重新输入或找回密码"); throw new IncorrectCredentialsException("用户名或密码有误,请重新输入或找回密码");
} }
// 如果密码验证通过说明登录认证成功调用iMemberService的updateLastLoginTime方法根据会员的唯一标识member.getId())更新会员的最后登录时间,记录会员此次登录的时间信息,方便后续业务分析等使用。
// 更新登录时间 // 更新登录时间
iMemberService.updateLastLoginTime(member.getId()); iMemberService.updateLastLoginTime(member.getId());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName());
return info;
}
} // 创建一个SimpleAuthenticationInfo对象将查询到的会员对象作为已认证的主体信息、用户输入的密码虽然密码在实际验证后通常不再需要但在这里按照Shiro的要求传入Shiro内部有相应的处理机制确保安全以及当前Realm的名称通过getName方法获取传入
// 这个对象会告知Shiro框架认证成功并将相关信息保存起来供后续使用例如在整个请求处理过程中判断用户是否已经认证等操作。
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName());
return info;
}
}

@ -12,15 +12,35 @@ import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreato
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
// @Configuration注解表明这个类是一个Spring的配置类用于定义各种与Shiro框架相关的Bean以及相关的配置信息
// Spring容器会在启动时读取这个类中的配置将其中定义的Bean创建并管理起来从而实现Shiro框架在Spring应用中的集成和功能配置。
@Configuration @Configuration
public class ShiroConfiguration { public class ShiroConfiguration {
// 创建一个静态的LinkedHashMap用于存储URL路径与对应的Shiro过滤器链定义
// 其中键String类型表示URL路径的匹配模式也是String类型表示对应的Shiro过滤器链的定义例如需要进行什么类型的认证、授权等操作
// 后续会将这个映射关系配置到ShiroFilterFactoryBean中以实现对不同URL请求的访问控制。
private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
/**
* MemberRealmSpringBean"shiroRealm"
* MemberRealmShiroAuthorizingRealm
* ShiroDefaultWebSecurityManagerShiro
*
* @return MemberRealmShiroSpringShiro使
*/
@Bean(name = "shiroRealm") @Bean(name = "shiroRealm")
public MemberRealm getShiroRealm() { public MemberRealm getShiroRealm() {
return new MemberRealm(); return new MemberRealm();
} }
/**
* EhCacheManagerSpringBean"shiroEhcacheManager"
* EhCacheManagerShiroEhcacheehcache-shiro.xml
* Shiro
*
* @return EhCacheManagerShiro使访
*/
@Bean(name = "shiroEhcacheManager") @Bean(name = "shiroEhcacheManager")
public EhCacheManager getEhCacheManager() { public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager(); EhCacheManager em = new EhCacheManager();
@ -28,11 +48,25 @@ public class ShiroConfiguration {
return em; return em;
} }
/**
* LifecycleBeanPostProcessorSpringBean"lifecycleBeanPostProcessor"
* LifecycleBeanPostProcessorShiroSpringShiroShiroBeanRealmSecurityManagerSpring
* Shiro使ShiroSpring
*
* @return LifecycleBeanPostProcessorShiroSpring
*/
@Bean(name = "lifecycleBeanPostProcessor") @Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor(); return new LifecycleBeanPostProcessor();
} }
/**
* DefaultAdvisorAutoProxyCreatorSpringAOPAspect-Oriented Programming
* 便ShiroproxyTargetClasstrue使
* Shiro
*
* @return DefaultAdvisorAutoProxyCreator使SpringShiro
*/
@Bean @Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
@ -40,6 +74,14 @@ public class ShiroConfiguration {
return daap; return daap;
} }
/**
* DefaultWebSecurityManagerSpringBean"securityManager"
* DefaultWebSecurityManagerShiroWebShiroRealm
* MemberRealmgetShiroRealmEhCacheManagergetEhCacheManagerDefaultWebSecurityManager
* 使使
*
* @return DefaultWebSecurityManagerShiroWebShiroShiroFilterFactoryBean使
*/
@Bean(name = "securityManager") @Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager() { public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
@ -48,6 +90,13 @@ public class ShiroConfiguration {
return dwsm; return dwsm;
} }
/**
* AuthorizationAttributeSourceAdvisorShiroAOP
* SpringAOPDefaultWebSecurityManagergetDefaultWebSecurityManager
* 访
*
* @return AuthorizationAttributeSourceAdvisorSpringAOPShiro
*/
@Bean @Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() { public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
@ -55,15 +104,30 @@ public class ShiroConfiguration {
return new AuthorizationAttributeSourceAdvisor(); return new AuthorizationAttributeSourceAdvisor();
} }
/**
* ShiroFilterFactoryBeanSpringBean"shiroFilter"
* ShiroFilterFactoryBeanShiroSpringWebShirogetDefaultWebSecurityManagerShiro
* URLsetLoginUrl"/login"访URLsetSuccessUrl"/index"
* filterChainDefinitionMapURLShiroURL访"authc"访"anon"访
* WebURL访
*
* @return ShiroFilterFactoryBeanWebShiro访使ShiroURL
*/
@Bean(name = "shiroFilter") @Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean() { public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager()); shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager());
shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setSuccessUrl("/index");
// 配置URL路径与Shiro过滤器链的映射关系例如
// "/member/**"路径下的请求都需要进行认证("authc"表示需要进行身份认证才能访问),这里的"/member/**"是一种Ant风格的路径匹配表达式表示以"/member/"开头的所有路径。
// "/**"路径下的请求都允许匿名访问("anon"表示任何人都可以直接访问,不需要进行身份认证),这里的"/**"表示匹配所有的URL路径通常用于配置一些公开的资源如静态资源、登录页面等可以直接访问。
filterChainDefinitionMap.put("/member/**", "authc"); filterChainDefinitionMap.put("/member/**", "authc");
filterChainDefinitionMap.put("/**", "anon"); filterChainDefinitionMap.put("/**", "anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean; return shiroFilterFactoryBean;
} }
} }

@ -6,16 +6,36 @@ import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
// @Component注解用于将这个类标记为Spring框架中的一个组件意味着Spring容器会在启动时扫描到这个类并将其作为一个Bean进行管理
// 使得这个类可以参与到Spring应用的依赖注入以及各种组件协作的流程中在这里它主要用于配置应用的错误页面相关逻辑。
@Component @Component
// 这个类实现了ErrorPageRegistrar接口该接口用于在Spring Boot应用中注册自定义的错误页面
// 通过实现接口中的registerErrorPages方法可以定义不同HTTP状态码对应的错误页面路径当应用出现相应的错误时会自动重定向到配置好的错误页面进行展示。
public class ErrorConfigurar implements ErrorPageRegistrar { public class ErrorConfigurar implements ErrorPageRegistrar {
/**
* ErrorPageRegistrarSpring BootWeb
* ErrorPageRegistry使便
*
* @param registry ErrorPageRegistrySpring BootHTTP
*
*/
@Override @Override
public void registerErrorPages(ErrorPageRegistry registry) { public void registerErrorPages(ErrorPageRegistry registry) {
ErrorPage[] errorPages=new ErrorPage[2]; // 创建一个ErrorPage类型的数组用于存储要配置的不同错误页面信息这里数组长度为2表示要配置两种不同HTTP状态码对应的错误页面
errorPages[0]=new ErrorPage(HttpStatus.NOT_FOUND,"/error404"); // 可以根据实际需求增加数组长度来配置更多的错误页面映射关系。
errorPages[1]=new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR,"/error500"); ErrorPage[] errorPages = new ErrorPage[2];
registry.addErrorPages(errorPages); // 配置第一个错误页面信息对应HTTP状态码为HttpStatus.NOT_FOUND即404状态码表示请求的资源未找到
// 当应用中出现资源未找到的错误时,会将用户重定向到"/error404"这个路径对应的页面进行展示,这里的"/error404"应该是项目中事先定义好的用于展示404错误信息的页面路径通常是一个HTML页面等
errorPages[0] = new ErrorPage(HttpStatus.NOT_FOUND, "/error404");
// 配置第二个错误页面信息对应HTTP状态码为HttpStatus.INTERNAL_SERVER_ERROR即500状态码表示服务器内部错误
// 当应用在执行过程中出现服务器内部错误(比如代码抛出未捕获的异常等情况)时,会将用户重定向到"/error500"这个路径对应的页面进行展示,同样,"/error500"也是项目中用于展示500错误信息的特定页面路径。
errorPages[1] = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error500");
// 通过调用ErrorPageRegistry对象的addErrorPages方法将配置好的包含错误页面映射关系的errorPages数组添加进去
// 这样Spring Boot应用的Web服务器就会根据这些配置在相应错误发生时重定向到对应的错误页面进行展示了。
registry.addErrorPages(errorPages);
} }
}
}

@ -11,31 +11,82 @@ import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import com.tamguo.common.utils.SystemConstant; import com.tamguo.common.utils.SystemConstant;
// @Component注解用于将这个类标记为Spring框架中的一个组件意味着Spring容器在启动时会扫描到这个类并将其作为一个Bean进行管理
// 使得该类能够参与到Spring应用的依赖注入以及整个应用的配置流程中在这里它主要负责对Thymeleaf相关的配置进行定制化设置。
@Component @Component
public class ThymeleafConfig implements EnvironmentAware{ // 这个类实现了EnvironmentAware接口通过实现该接口的setEnvironment方法可以获取到Spring应用的运行环境配置信息如配置文件中的各种属性值等
// 进而基于这些环境配置来进行相关的业务配置操作在本类中就是利用环境配置来设置Thymeleaf视图解析器中的一些静态变量。
public class ThymeleafConfig implements EnvironmentAware {
// 通过@Resource注解进行依赖注入将Spring的Environment对象注入到当前类中。Environment对象提供了访问应用配置属性的接口
// 可以通过它获取到配置文件如application.properties或application.yml等中定义的各种属性值以便在代码中使用这些配置信息进行相应的逻辑处理
// 例如获取服务器域名、不同模块的域名等配置信息用于后续设置到Thymeleaf的静态变量中。
@Resource @Resource
private Environment env; private Environment env;
/**
* 使@ResourceThymeleafViewResolverSpringThymeleafViewResolver
* ThymeleafEnvironmentSystemConstant
* MapThymeleafViewResolver使Thymeleaf使便使
*
* @param viewResolver ThymeleafViewResolverSpringThymeleaf
* 便Thymeleaf便访使
*/
@Resource @Resource
private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) { private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) {
if(viewResolver != null) { // 判断传入的ThymeleafViewResolver对象是否为空如果不为空则进行后续的静态变量配置操作避免空指针异常等情况。
Map<String, Object> vars = new HashMap<>(); if (viewResolver!= null) {
vars.put("domainName", env.getProperty("domain.name")); // 创建一个HashMap对象用于存储要设置到ThymeleafViewResolver中的静态变量键为变量名在Thymeleaf模板中使用的名称值为对应的变量值
vars.put("adminDomain", env.getProperty("admin.domain.name")); // 这些变量值可以是从配置文件中获取的属性值或者是代码中定义的常量值等通过设置静态变量方便在Thymeleaf模板中直接引用这些变量进行页面逻辑处理。
vars.put("memberDomain", env.getProperty("member.domain.name")); Map<String, Object> vars = new HashMap<>();
vars.put("PAPER_TYPE_ZHENTI", SystemConstant.ZHENGTI_PAPER_ID);
vars.put("PAPER_TYPE_MONI", SystemConstant.MONI_PAPER_ID); // 从Environment对象中获取名为"domain.name"的配置属性值,并将其作为"domainName"变量添加到vars集合中
vars.put("PAPER_TYPE_YATI", SystemConstant.YATI_PAPER_ID); // 在Thymeleaf模板中可以通过${domainName}的方式来引用这个变量,可能用于生成完整的域名相关链接等操作,具体用途取决于业务需求。
vars.put("PAPER_TYPE_MINGXIAO", SystemConstant.MINGXIAO_PAPER_ID); vars.put("domainName", env.getProperty("domain.name"));
vars.put("BEIJING_AREA_ID", SystemConstant.BEIJING_AREA_ID);
viewResolver.setStaticVariables(vars); // 从Environment对象中获取名为"admin.domain.name"的配置属性值,将其作为"adminDomain"变量添加到vars集合中
} // 这样在Thymeleaf模板中如果涉及到与管理员相关模块的域名使用场景时可以方便地通过${adminDomain}获取并使用这个变量,例如构建管理员模块的页面链接等情况。
vars.put("adminDomain", env.getProperty("admin.domain.name"));
// 类似地从Environment对象中获取名为"member.domain.name"的配置属性值,将其作为"memberDomain"变量添加到vars集合中
// 便于在Thymeleaf模板中针对会员相关模块的页面处理时通过${memberDomain}引用这个变量来构建相关的链接或者进行其他与域名相关的操作。
vars.put("memberDomain", env.getProperty("member.domain.name"));
// 从SystemConstant类中获取名为ZHENGTI_PAPER_ID的常量值并将其作为"PAPER_TYPE_ZHENTI"变量添加到vars集合中
// 在Thymeleaf模板中可以通过这个变量来表示某种试卷类型具体试卷类型含义由业务中SystemConstant类里的定义决定例如用于页面上试卷类型的展示或者根据试卷类型进行不同的页面逻辑处理等情况。
vars.put("PAPER_TYPE_ZHENTI", SystemConstant.ZHENGTI_PAPER_ID);
// 从SystemConstant类中获取名为MONI_PAPER_ID的常量值将其作为"PAPER_TYPE_MONI"变量添加到vars集合中
// 同样用于在Thymeleaf模板中处理与模拟试卷类型相关的页面逻辑比如根据模拟试卷类型展示特定的页面元素或者进行不同的交互操作等具体取决于业务设计。
vars.put("PAPER_TYPE_MONI", SystemConstant.MONI_PAPER_ID);
// 从SystemConstant类中获取名为YATI_PAPER_ID的常量值作为"PAPER_TYPE_YATI"变量添加到vars集合中
// 以便在Thymeleaf模板中针对押题试卷类型相关的页面渲染和逻辑处理时能够方便地引用这个变量例如根据是否是押题试卷类型来展示不同的提示信息等操作。
vars.put("PAPER_TYPE_YATI", SystemConstant.YATI_PAPER_ID);
// 从SystemConstant类中获取名为MINGXIAO_PAPER_ID的常量值将其作为"PAPER_TYPE_MINGXIAO"变量添加到vars集合中
// 在Thymeleaf模板中可以利用这个变量进行与名校试卷类型相关的页面展示和逻辑处理比如根据名校试卷类型来展示对应的学校标识或者试卷特色等信息具体由业务需求决定
vars.put("PAPER_TYPE_MINGXIAO", SystemConstant.MINGXIAO_PAPER_ID);
// 从SystemConstant类中获取名为BEIJING_AREA_ID的常量值将其作为"BEIJING_AREA_ID"变量添加到vars集合中
// 在Thymeleaf模板中可能用于涉及北京地区相关的页面逻辑处理例如判断是否是北京地区试卷、展示北京地区相关的特色内容等情况具体根据业务中对北京地区的定义和使用场景而定
vars.put("BEIJING_AREA_ID", SystemConstant.BEIJING_AREA_ID);
// 将包含了所有要设置的静态变量的vars集合通过调用ThymeleafViewResolver的setStaticVariables方法设置到ThymeleafViewResolver对象中
// 这样在后续使用Thymeleaf解析模板进行页面渲染时模板中就可以直接使用这些设置好的变量了方便进行各种页面逻辑的实现以及动态内容的展示等操作。
viewResolver.setStaticVariables(vars);
}
} }
/**
* EnvironmentAwareSpringSpringEnvironment
* Environmentenv使configureThymeleafStaticVarsenv访
*
* @param environment Springapplication.propertiesapplication.yml
* 便
*/
@Override @Override
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
env = environment; env = environment;
} }
}
}

@ -13,37 +13,93 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.tamguo.web.interceptor.MemberInterceptor; import com.tamguo.web.interceptor.MemberInterceptor;
import com.tamguo.web.interceptor.MenuInterceptor; import com.tamguo.web.interceptor.MenuInterceptor;
// @Configuration注解表明这个类是Spring框架中的一个配置类用于定义各种Bean以及配置Spring Web MVC相关的功能
// Spring容器会在启动时读取这个类中的配置信息将其中定义的Bean创建并管理起来同时应用配置的相关规则例如注册拦截器、配置资源处理等来定制Web应用的行为。
@Configuration @Configuration
// 这个类实现了WebMvcConfigurer接口该接口提供了一系列用于定制Spring Web MVC功能的方法通过重写这些方法
// 可以添加拦截器、配置资源处理器、处理跨域等操作这里重写了部分方法来实现特定的业务需求比如添加自定义拦截器以及配置文件资源的访问路径、设置Cookie序列化相关参数等。
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
// 通过@Value注解从配置文件例如application.properties或application.yml等中获取名为"file.storage.path"的属性值并注入到当前类的fileStoragePath变量中
// 这个属性值通常表示文件存储的路径,在后续配置文件资源访问时会用到,用于指定服务器上文件实际存放的位置,以便能正确地对外提供文件访问服务。
@Value("${file.storage.path}") @Value("${file.storage.path}")
private String fileStoragePath; private String fileStoragePath;
// 同样使用@Value注解从配置文件中获取名为"cookie.domian.name"的属性值注入到cookieDomianName变量中
// 该属性值一般是Cookie的域名设置用于确定Cookie在哪些域名下有效在配置Cookie序列化相关参数时会用到这个值确保Cookie的作用范围符合业务需求。
@Value("${cookie.domian.name}") @Value("${cookie.domian.name}")
private String cookieDomianName; private String cookieDomianName;
// 通过@Autowired注解自动注入MemberInterceptor类型的实例MemberInterceptor应该是一个自定义的拦截器类
// 用于拦截特定路径下的请求,可能在这些请求处理前进行一些与会员相关的逻辑验证、权限判断或者预处理操作,比如检查会员是否登录等情况,在后续配置拦截器时会将其添加到拦截器链中。
@Autowired @Autowired
private MemberInterceptor memberInterceptor; private MemberInterceptor memberInterceptor;
// 与上面类似,通过@Autowired注解注入MenuInterceptor类型的实例MenuInterceptor也是自定义的拦截器
// 可能用于拦截所有请求(从后续添加拦截器的配置来看),并针对菜单相关的业务逻辑进行处理,例如根据用户权限动态生成菜单、验证菜单访问权限等操作,同样会在后面配置到拦截器链中发挥作用。
@Autowired @Autowired
private MenuInterceptor menuInterceptor; private MenuInterceptor menuInterceptor;
/**
* WebMvcConfigureraddInterceptorsSpring Web MVC
* InterceptorRegistry
*
* @param registry InterceptorRegistry
* Spring Web MVC使
*/
@Override @Override
public void addInterceptors(InterceptorRegistry registry) { public void addInterceptors(InterceptorRegistry registry) {
// 将menuInterceptor添加到拦截器链中并指定其拦截的路径模式为"/**"这是一种Ant风格的路径匹配表达式表示拦截所有的请求路径
// 意味着所有进入应用的请求都会先经过menuInterceptor拦截器进行处理具体的处理逻辑在MenuInterceptor类中实现可能涉及菜单相关的各种业务逻辑操作比如权限验证、菜单数据准备等。
registry.addInterceptor(menuInterceptor).addPathPatterns("/**"); registry.addInterceptor(menuInterceptor).addPathPatterns("/**");
// 将memberInterceptor添加到拦截器链中并设置其拦截的路径模式为"/member/**",表示拦截以"/member/"开头的所有请求路径,
// 通常用于处理与会员相关模块的请求在这些请求被处理前memberInterceptor会执行相应的会员相关逻辑例如检查会员登录状态、会员权限验证等操作确保只有符合条件的会员请求才能继续往下执行。
registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**"); registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**");
} }
@Override /**
public void addResourceHandlers(ResourceHandlerRegistry registry) { * WebMvcConfigureraddResourceHandlersSpring Web MVC
registry.addResourceHandler("/files/**").addResourceLocations("file:"+fileStoragePath); * 访使访便访
} *
* @param registry ResourceHandlerRegistry访
@Bean * Spring Web MVC
public CookieSerializer defaultCookieSerializer(){ */
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer(); @Override
defaultCookieSerializer.setCookieName("sessionId"); public void addResourceHandlers(ResourceHandlerRegistry registry) {
defaultCookieSerializer.setDomainName(cookieDomianName); // 配置一个资源处理器,指定对外暴露的资源访问路径为"/files/**"这也是一种Ant风格的路径匹配表达式表示以"/files/"开头的所有路径都将被映射到对应的资源位置,
defaultCookieSerializer.setCookiePath("/"); // 例如客户端访问"/files/image.jpg"就会去查找实际对应的资源文件(具体查找位置由后面配置的实际存放位置决定)。
return defaultCookieSerializer; // 同时通过addResourceLocations方法指定资源实际存放的位置使用"file:"前缀加上之前注入的fileStoragePath变量表示文件存储的实际路径
} // 这样Spring Web MVC就能根据这个配置将对"/files/**"路径的请求转发到服务器上fileStoragePath指定的实际文件存储位置去查找并返回对应的资源文件了。
registry.addResourceHandler("/files/**").addResourceLocations("file:" + fileStoragePath);
} }
/**
* defaultCookieSerializer使@BeanSpringBean
* DefaultCookieSerializerCookieCookie
* 使使Spring SessionCookie
*
* @return DefaultCookieSerializerSpringCookieCookie使
* Cookie
*/
@Bean
public CookieSerializer defaultCookieSerializer() {
// 创建一个DefaultCookieSerializer实例它是Spring Session中用于对Cookie进行序列化处理的默认实现类
// 通过对其属性进行设置可以定制Cookie的各种参数如名称、域名、路径等以满足应用在不同环境下的需求。
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
// 设置Cookie的名称为"sessionId"在客户端浏览器中看到的Cookie的键名就会是"sessionId",用于标识会话相关的信息,
// 服务器通过这个名称来识别和获取对应的Cookie值从而获取会话相关的数据例如判断用户是否登录、获取用户会话中的其他属性等操作。
defaultCookieSerializer.setCookieName("sessionId");
// 设置Cookie的域名使用之前注入的cookieDomianName变量作为域名值确定Cookie在哪些域名下有效
// 这样可以根据业务需求限制Cookie的作用范围比如只在特定的子域名或者主域名下有效确保会话相关的信息在合适的域名范围内传递和使用。
defaultCookieSerializer.setDomainName(cookieDomianName);
// 设置Cookie的路径为"/"表示这个Cookie在整个应用的所有路径下都有效客户端在访问应用的任何页面时都会带上这个Cookie
// 方便服务器根据Cookie中的会话信息来处理不同路径下的请求维持用户的会话状态实现诸如登录状态保持等功能。
defaultCookieSerializer.setCookiePath("/");
return defaultCookieSerializer;
}
}

@ -2,33 +2,65 @@ package com.tamguo.utils;
import java.util.regex.Pattern; import java.util.regex.Pattern;
// 这个类主要用于判断访问设备的类型通过分析浏览器标识User-Agent字符串利用正则表达式来检测是否是移动设备包括手机和平板进行访问
// 提供了一个静态方法方便外部调用在Web应用开发中可用于根据不同的访问设备来提供适配的页面展示或者功能逻辑等情况。
public class BrowserUtils { public class BrowserUtils {
// \b 是单词边界(连着的两个(字母字符 与 非字母字符) 之间的逻辑上的间隔), // 定义一个表示手机设备的正则表达式字符串常量,用于匹配各种常见的手机操作系统、手机品牌及相关设备标识等信息,
// 字符串在编译时会被转码一次,所以是 "\\b" // 通过这个正则表达式可以判断浏览器标识User-Agent中是否包含手机相关的特征字符串从而确定是否是手机设备访问。
// \B 是单词内部逻辑间隔(连着的两个字母字符之间的逻辑上的间隔) // 以下是对正则表达式各部分的详细说明:
private static final String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i" // \b 是单词边界(连着的两个(字母字符 与 非字母字符之间的逻辑上的间隔在Java字符串中由于反斜杠本身需要转义所以表示为 "\\b"。
+"|windows (phone|ce)|blackberry" // 正则表达式中的 "ip(hone|od)" 用于匹配 "iphone" 或者 "ipod",表示苹果的手机或类似移动设备。
+"|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp" // "android" 用于匹配安卓操作系统相关的设备标识,常见于安卓手机等移动设备的浏览器标识中。
+"|laystation portable)|nokia|fennec|htc[-_]" // "opera m(ob|in)i" 用于匹配 Opera 浏览器的移动版本相关标识,比如 Opera Mobile 或者 Opera Mini也是常见的移动设备浏览器情况。
+"|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b"; // "windows (phone|ce)" 用于匹配微软的 Windows Phone 或者 Windows CE 系统相关的移动设备标识,过去常用于一些微软系的手机等设备。
// "blackberry" 用于匹配黑莓手机的相关标识,黑莓曾经是很知名的手机品牌,有其独特的浏览器标识特征可以通过这个来匹配。
// "s(ymbian|eries60|amsung)" 这里可以匹配塞班系统("symbian")、诺基亚塞班 S60 系列("series60")以及三星("samsung")部分旧的移动设备相关标识,这些都是曾经在手机领域常见的系统或品牌标识情况。
// "p(laybook|alm|rofile/midp" 等部分用于匹配黑莓的 PlayBook、Palm 设备以及一些基于 Java ME通过 "profile/midp" 匹配相关的配置和规范标识)的移动设备等情况,涵盖了多种不同类型的旧有移动设备特征。
// "laystation portable" 用于匹配索尼的 PlayStation Portable 设备,虽然它主要是游戏机,但也具备一定的浏览器功能,属于可移动使用的设备,所以也在匹配范围内。
// "nokia" 简单直接地匹配诺基亚手机相关标识,诺基亚曾经是全球最大的手机制造商之一,有很多不同型号手机的浏览器标识可以通过这个来识别是否是诺基亚手机访问。
// "fennec" 用于匹配火狐浏览器针对移动设备推出的 Fennec 版本相关标识,是移动版火狐浏览器的特征字符串。
// "htc[-_]" 用于匹配 HTC 品牌手机相关标识HTC 也是知名的手机制造商,其设备在浏览器标识中通常有特定的字符串特征,这里通过包含 "htc" 以及可能的连接字符("-" 或者 "_")来匹配。
// "mobile" 是一个比较通用的表示移动设备的标识字符串,很多移动设备的浏览器标识中都会包含这个单词来表明其是移动设备。
// "up.browser" 用于匹配某些特定的移动浏览器相关标识,可能是一些不太常见但存在的移动设备浏览器情况。
// "[1-4][0-9]{2}x[1-4][0-9]{2}" 这部分用于匹配一些特定分辨率格式的设备标识,常见于一些较老的移动设备通过分辨率相关信息来辅助判断是否是移动设备访问,不过这种方式相对比较模糊,只是作为一种补充的匹配特征。
private static final String phoneReg = "\\b(ip(hone|od)|android|opera m(ob|in)i"
+ "|windows (phone|ce)|blackberry"
+ "|s(ymbian|eries60|amsung)|p(laybook|alm|rofile/midp"
+ "|laystation portable)|nokia|fennec|htc[-_]"
+ "|mobile|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
private static final String tabletReg = "\\b(ipad|tablet|(Nexus 7)|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b"; // 定义一个表示平板设备的正则表达式字符串常量,用于匹配常见的平板电脑相关标识信息,
// 同样通过分析浏览器标识User-Agent中是否包含这些特征字符串来判断是否是平板设备访问其原理和手机设备的正则表达式类似只是匹配的具体字符串不同更侧重于平板相关的标识。
// 其中 "ipad" 用于匹配苹果的 iPad 平板电脑,这是市场占有率很高的平板设备,有其独特的浏览器标识特征。
// "tablet" 是一个比较通用的表示平板电脑的标识字符串,很多平板设备的浏览器标识中会包含它来表明自身是平板类型的设备。
// "(Nexus 7)" 用于匹配谷歌的 Nexus 7 平板电脑相关标识Nexus 系列平板曾经也比较受欢迎,有其特定的标识可以用于识别。
// "up.browser" 和 "[1-4][0-9]{2}x[1-4][0-9]{2}" 的含义与手机设备正则表达式中的类似,同样是作为辅助匹配一些特定情况的平板设备标识情况。
private static final String tabletReg = "\\b(ipad|tablet|(Nexus 7)|up.browser|[1-4][0-9]{2}x[1-4][0-9]{2})\\b";
//移动设备正则匹配:手机端、平板 // 使用Pattern类对phoneReg正则表达式进行编译创建一个Pattern对象phonePat用于后续在匹配手机设备相关标识时使用
private static Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE); // 通过编译后的Pattern对象可以更高效地进行正则匹配操作并且可以设置匹配的相关参数如这里通过第二个参数设置为 Pattern.CASE_INSENSITIVE 表示忽略大小写进行匹配),
private static Pattern tabletPat = Pattern.compile(tabletReg, Pattern.CASE_INSENSITIVE); // 这样在判断浏览器标识中是否包含手机设备相关特征时,就可以直接使用这个编译好的对象进行匹配操作了。
private static Pattern phonePat = Pattern.compile(phoneReg, Pattern.CASE_INSENSITIVE);
/** // 同样地对tabletReg正则表达式进行编译创建一个Pattern对象tabletPat用于匹配平板设备相关标识
* 访 // 设置匹配参数为忽略大小写Pattern.CASE_INSENSITIVE方便后续在分析浏览器标识时判断是否存在平板设备相关的特征字符串进而确定是否是平板设备访问。
* private static Pattern tabletPat = Pattern.compile(tabletReg, Pattern.CASE_INSENSITIVE);
* @param userAgent
* @return true:false:pc /**
*/ * User-Agent访
public static boolean isMobile(String userAgent){ * 使phonePattabletPat
if(null == userAgent){ * true false PC
userAgent = ""; *
} * @param userAgent HTTP "User-Agent" 访
return phonePat.matcher(userAgent).find() || tabletPat.matcher(userAgent).find(); * 访便
} * @return true
* false PC
*/
public static boolean isMobile(String userAgent) {
if (null == userAgent) {
userAgent = "";
}
return phonePat.matcher(userAgent).find() || tabletPat.matcher(userAgent).find();
}
} }

@ -5,85 +5,138 @@ import java.util.List;
import com.baomidou.mybatisplus.plugins.Page; import com.baomidou.mybatisplus.plugins.Page;
// 这个类主要用于对分页数据进行进一步的处理和包装,以便生成更适合前端展示分页相关交互元素(如下一页、上一页按钮显示控制以及页码列表展示等)的对象,
// 它基于MyBatis Plus的Page对象进行相关计算和属性设置提供了一种方便的方式将分页数据转换为前端易于处理和展示的格式。
public class PageUtils { public class PageUtils {
// 是否下一页按钮 // 用于标识是否显示下一页按钮初始化为false表示默认不显示下一页按钮后续会根据当前页码与总页数的关系来动态设置其值
// 如果当前页小于总页数就会将其设置为true表示需要显示下一页按钮方便用户点击跳转到下一页查看更多数据。
private Boolean isShowNextBtn = false; private Boolean isShowNextBtn = false;
// 是否上一页按钮 // 用于标识是否显示上一页按钮同样初始化为false默认不显示上一页按钮根据当前页码与总页数等情况来确定是否要显示
// 当当前页码大于1时说明不是第一页此时会将该属性设置为true以在前端展示上一页按钮供用户点击返回上一页查看数据。
private Boolean isShowPreBtn = false; private Boolean isShowPreBtn = false;
// 当前页 // 记录当前所在的用于在生成页码列表以及判断上下页按钮显示情况等操作时作为重要依据其值会从传入的Page对象中获取并设置。
private Integer currPageNum; private Integer currPageNum;
// 页码列表 // 用于存储要展示给用户的页码列表,例如可能是一个包含了连续页码数字以及省略号(用于表示中间省略部分页码)的字符串列表,
// 通过一定的逻辑计算生成,方便在前端展示分页导航栏,让用户可以直观地点击相应页码进行页面跳转,查看不同页的数据。
private List<String> pageNums; private List<String> pageNums;
// 总页数 // 表示分页数据的总页数从传入的Page对象中获取该值用于判断是否需要生成多页的页码列表以及确定上下页按钮的显示逻辑等操作
// 是整个分页逻辑处理中的一个关键数据指标。
private Integer totalPage; private Integer totalPage;
// 总数量 // 用于记录数据的总数量同样从Page对象中获取它反映了符合查询条件的所有数据的总数有助于前端展示一些相关提示信息如共多少条数据等以及辅助分页相关的逻辑判断。
private Integer total; private Integer total;
// 数据 // 存储当前页的数据列表从Page对象中获取对应页码的数据记录这是最终要展示给用户的数据内容会传递给前端进行列表展示等操作。
private List<?> list; private List<?> list;
public static PageUtils getPage(Page<?> page){ /**
* MyBatis PlusPagePageUtilsPageUtils
* PagePageUtils
*
*
* @param page MyBatis PlusPage
* PageUtils便
* @return PageUtils
*/
public static PageUtils getPage(Page<?> page) {
PageUtils pg = new PageUtils(); PageUtils pg = new PageUtils();
if(page.getCurrent() > 1){
// 判断当前页码是否大于1如果大于1说明不是第一页此时需要显示上一页按钮将PageUtils对象的isShowPreBtn属性设置为true
// 这样前端在展示分页导航栏时就会显示上一页按钮,方便用户点击返回上一页查看数据。
if (page.getCurrent() > 1) {
pg.setIsShowPreBtn(true); pg.setIsShowPreBtn(true);
} }
if(page.getCurrent() < page.getPages()){
// 判断当前页码是否小于总页数如果小于总页数意味着还有下一页数据需要显示下一页按钮将PageUtils对象的isShowNextBtn属性设置为true
// 以便前端展示下一页按钮,供用户点击跳转到下一页查看更多数据。
if (page.getCurrent() < page.getPages()) {
pg.setIsShowNextBtn(true); pg.setIsShowNextBtn(true);
} }
List<String> pgNums = new ArrayList<>(); List<String> pgNums = new ArrayList<>();
if(page.getPages() > 1){
if(page.getPages() > 10){ // 判断总页数是否大于1如果大于1说明有多页数据需要生成相应的页码列表进行展示进入下面不同情况的页码列表生成逻辑
// 如果总页数等于1说明只有一页数据页码列表就只包含一个页码通常就是1后续会在循环中进行相应处理。
if (page.getPages() > 1) {
// 如果总页数大于10说明页码较多采用一种简化展示的方式生成页码列表通常会显示首页、前几页、中间用省略号表示、当前页及其前后几页、尾页等关键页码信息
// 方便用户在较多页码情况下能快速定位到想要查看的页面附近进行跳转操作。
if (page.getPages() > 10) {
// 添加页码1表示首页一般在分页导航栏中会作为第一个可点击的页码展示方便用户快速回到第一页查看数据。
pgNums.add("1"); pgNums.add("1");
// 添加页码2作为前面几页中的一个页码展示让用户可以较方便地查看前面几页的数据情况。
pgNums.add("2"); pgNums.add("2");
// 添加页码3同样是作为前面几页的页码展示丰富前面部分页码的展示内容便于用户操作。
pgNums.add("3"); pgNums.add("3");
// 添加省略号,表示中间省略了部分页码,因为总页数较多,全部展示会显得很冗长,通过省略号提示用户中间还有其他页码存在。
pgNums.add("..."); pgNums.add("...");
if(page.getCurrent() == page.getPages()){
pgNums.add(((Integer)(page.getCurrent() - 2)).toString()); // 判断当前页码是否等于总页数,如果等于总页数,说明当前是最后一页,页码列表的生成逻辑稍有不同,
pgNums.add(((Integer)(page.getCurrent() - 1)).toString()); // 需要展示当前页(最后一页)及其前面紧邻的两页页码,方便用户查看临近的页面情况或者进行前后页跳转等操作。
pgNums.add(((Integer)page.getCurrent()).toString()); if (page.getCurrent() == page.getPages()) {
pgNums.add(((Integer) (page.getCurrent() - 2)).toString());
pgNums.add(((Integer) (page.getCurrent() - 1)).toString());
pgNums.add(((Integer) page.getCurrent()).toString());
} else { } else {
pgNums.add(((Integer)(page.getCurrent() - 1)).toString()); // 如果当前页码不是总页数,说明在中间页面,此时展示当前页及其前后各一页的页码,方便用户在当前页附近进行页码跳转查看数据。
pgNums.add(((Integer)(page.getCurrent())).toString()); pgNums.add(((Integer) (page.getCurrent() - 1)).toString());
pgNums.add(((Integer)(page.getCurrent() + 1)).toString()); pgNums.add(((Integer) (page.getCurrent())).toString());
pgNums.add(((Integer) (page.getCurrent() + 1)).toString());
} }
}else{ } else {
// 如果总页数小于等于10说明页码较少可以完整地展示所有页码通过循环依次将页码数字转换为字符串并添加到页码列表中
// 方便前端展示所有可点击的页码,让用户能方便地在各页之间进行跳转查看数据。
Integer n = 1; Integer n = 1;
if(page.getTotal() > 0){ if (page.getTotal() > 0) {
while(true){ while (true) {
pgNums.add(n.toString()); pgNums.add(n.toString());
if(n >= page.getPages()){ if (n >= page.getPages()) {
break; break;
} }
n ++; n++;
} }
} }
} }
} else { } else {
// 如果总页数等于1同样通过循环添加唯一的页码就是1到页码列表中虽然只有一页数据但前端展示时也需要展示对应的页码信息保持分页展示逻辑的一致性。
Integer n = 1; Integer n = 1;
if(page.getTotal() > 0){ if (page.getTotal() > 0) {
while(true){ while (true) {
pgNums.add(n.toString()); pgNums.add(n.toString());
if(n >= page.getPages()){ if (n >= page.getPages()) {
break; break;
} }
n ++; n++;
} }
} }
} }
// 将生成好的页码列表设置到PageUtils对象的pageNums属性中以便前端可以获取并展示这个页码列表实现分页导航栏中页码部分的展示功能。
pg.setPageNums(pgNums); pg.setPageNums(pgNums);
// 将Page对象中的当前页数据列表设置到PageUtils对象的list属性中这些数据就是要展示给用户查看的具体内容
// 前端可以根据这个属性获取数据并进行列表展示等操作,呈现当前页的详细数据信息。
pg.setList(page.getRecords()); pg.setList(page.getRecords());
pg.setCurrPageNum(((Integer)page.getCurrent()));
pg.setTotal(((Integer)page.getTotal())); // 将Page对象中的当前页码赋值给PageUtils对象的currPageNum属性用于前端展示当前所在页码以及在一些分页相关逻辑判断如下一页、上一页按钮显示等中作为依据。
pg.setTotalPage(((Integer)page.getPages())); pg.setCurrPageNum(((Integer) page.getCurrent()));
// 将Page对象中的总数据数量赋值给PageUtils对象的total属性方便前端展示数据总量相关提示信息如共多少条数据等让用户对数据整体情况有直观了解。
pg.setTotal(((Integer) page.getTotal()));
// 将Page对象中的总页数赋值给PageUtils对象的totalPage属性在前端分页展示逻辑中例如判断是否显示上下页按钮、生成页码列表等操作时会用到这个总页数信息
// 确保分页相关的展示和操作符合实际的总页数情况。
pg.setTotalPage(((Integer) page.getPages()));
return pg; return pg;
} }
// 以下是各个属性的Getter和Setter方法用于获取和设置PageUtils对象的各个属性值
// 遵循JavaBean的规范方便在其他地方对PageUtils对象的属性进行访问和修改操作例如在前端获取相关属性进行展示或者在其他业务逻辑中根据需要设置属性值等情况。
public Boolean getIsShowNextBtn() { public Boolean getIsShowNextBtn() {
return isShowNextBtn; return isShowNextBtn;
@ -109,42 +162,34 @@ public class PageUtils {
this.list = list; this.list = list;
} }
public Boolean getIsShowPreBtn() { public Boolean getIsShowPreBtn() {
return isShowPreBtn; return isShowPreBtn;
} }
public void setIsShowPreBtn(Boolean isShowPreBtn) { public void setIsShowPreBtn(Boolean isShowPreBtn) {
this.isShowPreBtn = isShowPreBtn; this.isShowPreBtn = isShowPreBtn;
} }
public Integer getCurrPageNum() { public Integer getCurrPageNum() {
return currPageNum; return currPageNum;
} }
public void setCurrPageNum(Integer currPageNum) { public void setCurrPageNum(Integer currPageNum) {
this.currPageNum = currPageNum; this.currPageNum = currPageNum;
} }
public Integer getTotalPage() { public Integer getTotalPage() {
return totalPage; return totalPage;
} }
public void setTotalPage(Integer totalPage) { public void setTotalPage(Integer totalPage) {
this.totalPage = totalPage; this.totalPage = totalPage;
} }
public Integer getTotal() { public Integer getTotal() {
return total; return total;
} }
public void setTotal(Integer total) { public void setTotal(Integer total) {
this.total = total; this.total = total;
} }

@ -6,38 +6,91 @@ import org.apache.shiro.subject.Subject;
import com.tamguo.modules.member.model.MemberEntity; import com.tamguo.modules.member.model.MemberEntity;
// 这个类是一个工具类主要提供了一系列基于Apache Shiro框架的便捷方法用于获取与当前用户相关的各种信息如用户实体对象、用户ID等、操作会话设置和获取会话属性以及判断用户登录状态、执行登出操作等
// 通过这些静态方法可以在项目的不同地方方便地与Shiro框架进行交互减少重复代码使得对用户相关功能的处理更加简洁和统一。
public class ShiroUtils { public class ShiroUtils {
/**
* ShiroSession
* ShiroSession
* SecurityUtils.getSubject()SubjectgetSession()便
*
* @return Shiro使
*/
public static Session getSession() { public static Session getSession() {
return SecurityUtils.getSubject().getSession(); return SecurityUtils.getSubject().getSession();
} }
/**
* SubjectShiroSubject访
* SecurityUtils.getSubject()
*
* @return 访Subject便Shiro
*/
public static Subject getSubject() { public static Subject getSubject() {
return SecurityUtils.getSubject(); return SecurityUtils.getSubject();
} }
/**
* MemberEntityShiroSubjectPrincipal
* MemberEntityPrincipalSecurityUtils.getSubject().getPrincipal()PrincipalMemberEntity
* 便使
*
* @return MemberEntityPrincipalnullClassCastException使isLogin
*/
public static MemberEntity getMember() { public static MemberEntity getMember() {
return (MemberEntity)SecurityUtils.getSubject().getPrincipal(); return (MemberEntity) SecurityUtils.getSubject().getPrincipal();
} }
/**
* IDgetMember()MemberEntitygetId()ID
* IDID
*
* @return IDMemberEntityIDgetId()getMembernull
* isLogin
*/
public static String getMemberId() { public static String getMemberId() {
return getMember().getId(); return getMember().getId();
} }
/**
* ShirogetSession()setAttributekeyvalue
* 便使
*
* @param key Map便
* @param value
*/
public static void setSessionAttribute(Object key, Object value) { public static void setSessionAttribute(Object key, Object value) {
getSession().setAttribute(key, value); getSession().setAttribute(key, value);
} }
/**
* ShirogetSessiongetAttributekey
* setSessionAttribute便
*
* @param key nullnull
* @return null
*/
public static Object getSessionAttribute(Object key) { public static Object getSessionAttribute(Object key) {
return getSession().getAttribute(key); return getSession().getAttribute(key);
} }
/**
* SubjectPrincipalnull
* PrincipalnullPrincipalnull访
* 访访
*
* @return SubjectPrincipalnulltruePrincipalnullfalse
*/
public static boolean isLogin() { public static boolean isLogin() {
return SecurityUtils.getSubject().getPrincipal() != null; return SecurityUtils.getSubject().getPrincipal()!= null;
} }
/**
* SecurityUtils.getSubject().logout()Shiro
*
*/
public static void logout() { public static void logout() {
SecurityUtils.getSubject().logout(); SecurityUtils.getSubject().logout();
} }
}
}

@ -8,53 +8,79 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
// @Component注解用于将这个类标记为Spring框架中的一个组件意味着Spring容器会在启动时扫描到这个类并将其作为一个Bean进行管理
// 使其能够参与到Spring应用的依赖注入以及整个应用的请求处理流程中在这里它作为一个拦截器用于拦截特定路径下的请求并进行会员相关的验证逻辑处理。
@Component @Component
public class MemberInterceptor extends HandlerInterceptorAdapter{ // 这个类继承自HandlerInterceptorAdapter类HandlerInterceptorAdapter是Spring Web MVC中提供的一个用于方便实现拦截器功能的抽象类
// 通过重写其中的方法可以在请求处理的不同阶段如请求前、请求处理后、视图渲染后等进行拦截并添加自定义的逻辑这里主要重写了preHandle方法用于在请求处理前进行会员登录状态的验证等操作。
public class MemberInterceptor extends HandlerInterceptorAdapter {
/** "重定向URL"参数名称 */ // 定义一个表示重定向URL参数名称的常量字符串用于在重定向到登录页面时将当前请求的URL作为参数传递过去以便用户登录成功后可以回到原本要访问的页面
// 在后续构建重定向URL的逻辑中会使用这个参数名称来添加对应的参数信息。
private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl"; private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl";
/** 默认登录URL */ // 定义一个表示默认登录URL的常量字符串初始化为"/login.html"如果没有通过配置文件指定具体的登录URL就会使用这个默认值作为登录页面的路径
// 在后续处理用户未登录需要重定向到登录页面的情况时会参考这个默认值或者配置后的登录Url值来构建重定向的目标URL。
private static final String DEFAULT_LOGIN_URL = "/login.html"; private static final String DEFAULT_LOGIN_URL = "/login.html";
/** 登录URL */ // 用于存储登录URL的变量初始化为默认登录URLDEFAULT_LOGIN_URL后续可以通过配置文件注入具体的值来覆盖默认值
// 确定实际应用中用户登录页面的具体访问路径,以便在需要引导未登录用户去登录时,能够准确地重定向到正确的登录页面。
private String loginUrl = DEFAULT_LOGIN_URL; private String loginUrl = DEFAULT_LOGIN_URL;
// 通过@Value注解从配置文件例如application.properties或application.yml等中获取名为"member.domain.name"的属性值并注入到memberDomainName变量中
// 这个属性值通常表示会员相关模块所在的域名在构建重定向到登录页面等相关的URL时会用到用于确保重定向的URL是在正确的域名下符合业务的域名配置要求。
@Value("${member.domain.name}") @Value("${member.domain.name}")
private String memberDomainName; private String memberDomainName;
/** /**
* * HandlerInterceptorAdapterpreHandle
* * "currMember"AjaxGET
* @param request * 访
* HttpServletRequest *
* @param response * @param request HttpServletRequest
* HttpServletResponse * URL
* @param handler * @param response HttpServletResponse
* * Ajax访
* @return * @param handler Controller
* truefalse
* @return truefalse
* truefalse
* @throws Exception I/OSpring Web MVC
*/ */
@Override @Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Object currMember = request.getSession().getAttribute("currMember"); // 从HttpServletRequest对象的会话Session中获取名为"currMember"的属性值,通常假设在用户登录成功后,会将登录的会员相关信息(比如会员对象等)存储在这个会话属性中,
if (currMember != null) { // 通过判断这个属性是否为null来确定用户是否已经登录如果不为null则说明用户已登录允许请求继续执行返回true。
Object currMember = request.getSession().getAttribute("currMember");
if (currMember!= null) {
return true; return true;
} else { } else {
// 获取请求头中名为"X-Requested-With"的字段值这个字段常用于判断请求是否是Ajax请求很多JavaScript框架在发送Ajax请求时会设置这个请求头
// 如果该字段值不为null且等于"XMLHttpRequest"不区分大小写说明当前请求是Ajax请求对于Ajax请求的处理逻辑与普通页面请求稍有不同如下所述。
String requestType = request.getHeader("X-Requested-With"); String requestType = request.getHeader("X-Requested-With");
if (requestType != null && requestType.equalsIgnoreCase("XMLHttpRequest")) { if (requestType!= null && requestType.equalsIgnoreCase("XMLHttpRequest")) {
// 如果是Ajax请求且用户未登录向响应头中添加一个名为"loginStatus"的字段,值为"accessDenied"用于告知前端JavaScript代码等用户未登录禁止访问
// 然后通过response.sendError方法发送一个HTTP状态码为HttpServletResponse.SC_FORBIDDEN即403状态码表示禁止访问的错误响应
// 前端接收到这个响应后可以根据响应头中的信息以及状态码进行相应的提示或者处理比如弹出提示框告知用户需要登录等操作最后返回false拦截该请求不让其继续执行后续的业务处理器。
response.addHeader("loginStatus", "accessDenied"); response.addHeader("loginStatus", "accessDenied");
response.sendError(HttpServletResponse.SC_FORBIDDEN); response.sendError(HttpServletResponse.SC_FORBIDDEN);
return false; return false;
} else { } else {
// 如果不是Ajax请求再根据请求的方法如GET、POST等进行不同的重定向处理这里先判断请求方法是否是GET请求。
if (request.getMethod().equalsIgnoreCase("GET")) { if (request.getMethod().equalsIgnoreCase("GET")) {
String redirectUrl = request.getQueryString() != null ? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI(); // 如果是GET请求构建一个重定向的URL用于将未登录的用户重定向到登录页面并传递当前请求的URL作为参数以便用户登录成功后能回到原本要访问的页面具体构建逻辑如下
// 首先判断请求中是否有查询字符串即URL中?后面的部分如果有则将请求的完整路径包括请求URI和查询字符串作为重定向URL的一部分
// 如果没有查询字符串就只使用请求的URI作为重定向URL的一部分然后通过URLEncoder将这个重定向URL进行UTF-8编码防止中文等特殊字符出现编码问题
// 最后将编码后的重定向URL作为参数参数名为之前定义的REDIRECT_URL_PARAMETER_NAME添加到登录URL后面构建出完整的重定向URL格式类似memberDomainName + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + 编码后的重定向URL。
String redirectUrl = request.getQueryString()!= null? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI();
response.sendRedirect(memberDomainName + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8")); response.sendRedirect(memberDomainName + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8"));
} else { } else {
// 如果请求方法不是GET请求比如是POST等其他请求方法则直接重定向到登录页面不传递当前请求的URL作为参数了因为非GET请求通常不适合这样处理具体根据业务需求而定
// 重定向的URL格式为memberDomainName + request.getContextPath() + loginUrl其中request.getContextPath()获取的是当前应用在服务器上的上下文路径确保重定向的登录URL是在正确的应用路径下。
response.sendRedirect(memberDomainName + request.getContextPath() + loginUrl); response.sendRedirect(memberDomainName + request.getContextPath() + loginUrl);
} }
return false; return false;
} }
} }
} }
}
}

@ -19,82 +19,149 @@ import com.tamguo.modules.tiku.service.IMenuService;
import com.tamguo.modules.tiku.service.IPaperService; import com.tamguo.modules.tiku.service.IPaperService;
import com.tamguo.modules.tiku.service.ISchoolService; import com.tamguo.modules.tiku.service.ISchoolService;
/** // @Component注解用于将这个类标记为Spring框架中的一个组件意味着Spring容器会在启动时扫描到这个类并将其作为一个Bean进行管理
* // 使其能够参与到Spring应用的依赖注入以及整个Web应用的请求处理流程中在这里它作为一个拦截器组件用于拦截请求并处理与菜单相关的数据加载等操作。
*
*/
@Component @Component
// 这个类实现了HandlerInterceptor接口HandlerInterceptor接口用于在Spring Web MVC中定义拦截器
// 通过实现该接口的三个方法preHandle、postHandle、afterCompletion可以在请求处理的不同阶段请求前、请求处理后但视图渲染前、整个请求结束后进行拦截并执行自定义的逻辑
// 比如进行权限验证、数据准备、资源清理等操作,此处主要用于在请求处理过程中准备菜单相关以及其他页面展示需要的数据,并设置到请求属性中供视图渲染使用。
public class MenuInterceptor implements HandlerInterceptor { public class MenuInterceptor implements HandlerInterceptor {
// 通过@Resource注解进行依赖注入将IMenuService接口的实现类实例注入到当前类中IMenuService用于处理与菜单相关的业务逻辑
// 例如查询不同类型的菜单数据(全部菜单、左侧菜单、底部菜单等),在后续的拦截器方法中会调用其相关方法来获取菜单数据并设置到请求属性中,方便视图渲染时展示菜单信息。
@Resource @Resource
private IMenuService iMenuService; private IMenuService iMenuService;
// 同样通过@Resource注解注入IPaperService接口的实现类实例IPaperService用于处理与试卷相关的业务逻辑
// 比如查询不同类型的试卷数据(历史试卷、模拟试卷、热门试卷等),后续会调用其方法获取试卷数据并设置到请求属性,供页面展示试卷相关内容使用。
@Resource @Resource
private IPaperService iPaperService; private IPaperService iPaperService;
// 使用@Resource注解注入ISchoolService接口的实现类实例ISchoolService用于处理与学校相关的业务逻辑
// 像查询名校试卷、名校列表等数据,在拦截器的业务逻辑中会利用它获取相应的数据并添加到请求属性里,便于在页面上展示学校相关信息。
@Resource @Resource
private ISchoolService iSchoolService; private ISchoolService iSchoolService;
// 通过@Resource注解注入IAdService接口的实现类实例IAdService用于处理与广告相关的业务逻辑
// 在这里用于获取所有广告数据,然后将广告数据设置到请求属性中,使得页面能够展示相应的广告内容,实现广告投放等功能。
@Resource @Resource
private IAdService iAdService; private IAdService iAdService;
@Override /**
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) * HandlerInterceptorController
throws Exception { * true
//在请求处理之前进行调用Controller方法调用之前 * 访truefalse
return true; *
} * @param request HttpServletRequest
*
@Override * @param response HttpServletResponse
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, * 403访
ModelAndView modelAndView) throws Exception { * @param handler Controller
//请求处理之后进行调用但是在视图被渲染之前Controller方法调用之后 * truefalse
List<MenuEntity> result = iMenuService.findMenus(); * @return truefalse
request.setAttribute("menuList", result); * @throws Exception Spring Web MVC
*/
// 获取全部菜单 @Override
List<MenuEntity> allMenuList = iMenuService.findAllMenus(); public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
request.setAttribute("allMenuList", allMenuList); throws Exception {
//在请求处理之前进行调用Controller方法调用之前
// 获取左侧菜单 return true;
List<MenuEntity> leftMenuList = iMenuService.findLeftMenus(); }
request.setAttribute("leftMenuList", leftMenuList);
/**
// 获取底部菜单 * HandlerInterceptorController
List<MenuEntity> footerMenuList = iMenuService.findFooterMenus(); * 便使
request.setAttribute("footerMenuList", footerMenuList); * 广使
*
// 获取首页专区 * @param request HttpServletRequest便使
List<MenuEntity> chapterMenuList = iMenuService.findChapterMenus(); * @param response HttpServletResponse
request.setAttribute("chapterMenuList", chapterMenuList); * @param handler Controller
* @param modelAndView ModelAndViewSpring Web MVC
// 获取首页历年真题试卷 *
List<PaperEntity> historyPaperList = iPaperService.findHistoryPaper(); * @throws Exception Spring Web MVC
request.setAttribute("historyPaperList", historyPaperList); */
@Override
// 获取首页模拟试卷 public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
List<PaperEntity> simulationPaperList = iPaperService.findSimulationPaper(); ModelAndView modelAndView) throws Exception {
request.setAttribute("simulationPaperList", simulationPaperList); //请求处理之后进行调用但是在视图被渲染之前Controller方法调用之后
// 获取热门试卷 // 调用iMenuService的findMenus方法从数据库等数据源中查询出需要展示的菜单数据具体查询逻辑由IMenuService的实现类决定
List<PaperEntity> hotPaperList = iPaperService.findHotPaper(SystemConstant.BEIJING_AREA_ID); // 将查询到的菜单数据以List<MenuEntity>形式返回)设置到请求的属性中,属性名为"menuList"这样在视图如JSP、Thymeleaf等模板页面中就可以通过这个属性名获取到菜单数据进行展示
request.setAttribute("hotPaperList", hotPaperList); // 例如在页面上循环展示菜单列表等操作,方便用户进行页面导航等功能。
List<MenuEntity> result = iMenuService.findMenus();
// 获取首页名校试卷 request.setAttribute("menuList", result);
List<SchoolEntity> eliteSchoolPaperList = iSchoolService.findEliteSchoolPaper(SystemConstant.BEIJING_AREA_ID);
request.setAttribute("eliteSchoolPaperList", eliteSchoolPaperList); // 调用iMenuService的findAllMenus方法获取全部的菜单数据与上面的findMenus方法可能查询条件等有所不同这里是获取所有菜单信息
// 将查询到的全部菜单数据以List<MenuEntity>形式设置到请求的属性中,属性名为"allMenuList",便于在页面上根据不同需求展示全部菜单内容,比如用于管理后台查看所有菜单情况等操作。
// 获取首页名校列表 List<MenuEntity> allMenuList = iMenuService.findAllMenus();
List<SchoolEntity> eliteSchoolList = iSchoolService.findEliteSchool(); request.setAttribute("allMenuList", allMenuList);
request.setAttribute("eliteSchoolList", eliteSchoolList);
// 调用iMenuService的findLeftMenus方法查询出要展示在页面左侧的菜单数据通常是用于构建页面左侧导航栏等功能的菜单列表
// 获取所有广告 // 把查询到的左侧菜单数据以List<MenuEntity>形式添加到请求属性中,属性名为"leftMenuList",使得页面在渲染左侧导航栏时能够获取并展示这些菜单信息,方便用户进行页面操作和导航。
List<AdEntity> adList = iAdService.findAll(); List<MenuEntity> leftMenuList = iMenuService.findLeftMenus();
request.setAttribute("adList", adList); request.setAttribute("leftMenuList", leftMenuList);
} // 调用iMenuService的findFooterMenus方法获取要展示在页面底部的菜单数据用于构建页面底部的菜单部分比如一些友情链接、版权信息等相关的菜单展示
// 将获取到的底部菜单数据以List<MenuEntity>形式设置到请求属性中,属性名为"footerMenuList",以便页面在渲染底部部分时能够展示这些菜单内容,完善页面的整体布局和功能。
@Override List<MenuEntity> footerMenuList = iMenuService.findFooterMenus();
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) request.setAttribute("footerMenuList", footerMenuList);
throws Exception {
//在整个请求结束之后被调用也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作) // 调用iMenuService的findChapterMenus方法查询出用于首页专区展示的菜单数据可能是首页中某个特定区域的菜单具体功能和展示形式由业务需求决定
} // 将查询到的这些菜单数据以List<MenuEntity>形式添加到请求属性中,属性名为"chapterMenuList",使得首页在渲染对应专区时能够获取并展示这些菜单信息,丰富首页的展示内容和功能。
List<MenuEntity> chapterMenuList = iMenuService.findChapterMenus();
request.setAttribute("chapterMenuList", chapterMenuList);
// 调用iPaperService的findHistoryPaper方法从数据库等数据源中查询出首页要展示的历年真题试卷数据具体的查询逻辑根据业务中对历年真题试卷的定义和筛选条件而定
// 将查询到的历年真题试卷数据以List<PaperEntity>形式返回)设置到请求属性中,属性名为"historyPaperList",方便首页在展示试卷相关内容时获取并渲染这些真题试卷信息,供用户查看和选择。
List<PaperEntity> historyPaperList = iPaperService.findHistoryPaper();
request.setAttribute("historyPaperList", historyPaperList);
// 调用iPaperService的findSimulationPaper方法获取首页要展示的模拟试卷数据同样根据业务对模拟试卷的定义和筛选条件进行查询
// 将查询到的模拟试卷数据以List<PaperEntity>形式设置到请求属性中,属性名为"simulationPaperList",使得首页能够展示模拟试卷相关内容,满足用户查看和练习模拟试卷的需求。
List<PaperEntity> simulationPaperList = iPaperService.findSimulationPaper();
request.setAttribute("simulationPaperList", simulationPaperList);
// 调用iPaperService的findHotPaper方法传入SystemConstant.BEIJING_AREA_ID可能是表示北京地区的一个常量标识具体由业务中SystemConstant类定义
// 查询出对应地区这里是北京地区的热门试卷数据根据业务规则确定热门试卷的筛选条件等将查询到的热门试卷数据以List<PaperEntity>形式设置到请求属性中,属性名为"hotPaperList"
// 方便在页面上展示热门试卷相关内容,吸引用户关注和选择相应的试卷进行练习等操作。
List<PaperEntity> hotPaperList = iPaperService.findHotPaper(SystemConstant.BEIJING_AREA_ID);
request.setAttribute("hotPaperList", hotPaperList);
// 调用iSchoolService的findEliteSchoolPaper方法传入SystemConstant.BEIJING_AREA_ID查询出北京地区的名校试卷数据按照业务中对名校试卷的定义和筛选条件进行查询
// 将查询到的名校试卷数据以List<SchoolEntity>形式返回这里可能SchoolEntity中包含了名校以及对应的试卷相关信息具体由业务实体类定义决定设置到请求属性中属性名为"eliteSchoolPaperList"
// 以便在首页等页面展示名校试卷相关内容,体现名校特色试卷,供用户查看和选择练习。
List<SchoolEntity> eliteSchoolPaperList = iSchoolService.findEliteSchoolPaper(SystemConstant.BEIJING_AREA_ID);
request.setAttribute("eliteSchoolPaperList", eliteSchoolPaperList);
// 调用iSchoolService的findEliteSchool方法查询出所有的名校列表数据根据业务中对名校的定义和筛选条件从数据库等数据源获取
// 将查询到的名校列表数据以List<SchoolEntity>形式设置到请求属性中,属性名为"eliteSchoolList",方便在页面上展示名校相关信息,比如在选择学校的下拉框、名校推荐列表等地方展示这些名校信息。
List<SchoolEntity> eliteSchoolList = iSchoolService.findEliteSchool();
request.setAttribute("eliteSchoolList", eliteSchoolList);
// 调用iAdService的findAll方法获取所有的广告数据具体的广告数据来源和查询逻辑由IAdService的实现类决定
// 将获取到的广告数据以List<AdEntity>形式设置到请求属性中,属性名为"adList",使得页面能够展示相应的广告内容,实现广告投放功能,提升业务宣传等效果。
List<AdEntity> adList = iAdService.findAll();
request.setAttribute("adList", adList);
}
/**
* HandlerInterceptorDispatcherServlet
*
*
*
* @param request HttpServletRequest
* 使
* @param response HttpServletResponse
*
* @param handler 使
* @param ex ExceptionpreHandlepostHandleController
*
* @throws Exception Spring Web MVC
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
//在整个请求结束之后被调用也就是在DispatcherServlet 渲染了对应的视图之后执行(主要是用于进行资源清理工作)
}
} }

@ -34,116 +34,203 @@ import com.tamguo.modules.member.model.MemberEntity;
import com.tamguo.modules.member.service.IMemberService; import com.tamguo.modules.member.service.IMemberService;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端。
@Controller @Controller
public class AccountController { public class AccountController {
// 通过@Autowired注解自动注入IMemberService接口的实现类实例IMemberService用于处理与会员相关的各种业务逻辑
// 比如查询会员信息、更新会员信息、修改会员密码等操作,在这个控制器的多个方法中会调用其相关方法来实现具体的会员业务功能。
@Autowired @Autowired
public IMemberService memberService; public IMemberService memberService;
// 通过@Value注解从配置文件例如application.properties或application.yml等中获取名为"file.storage.path"的属性值并注入到fileStoragePath变量中
// 这个属性值通常表示文件存储的路径,在文件上传相关的业务逻辑中会用到,用于确定上传文件在服务器端实际保存的位置。
@Value("${file.storage.path}") @Value("${file.storage.path}")
private String fileStoragePath; private String fileStoragePath;
// 同样使用@Value注解从配置文件中获取名为"domain.name"的属性值注入到domainName变量中
// 该属性值一般是域名相关的配置在文件上传成功后返回文件的访问路径等场景中会用到用于构建完整的文件访问URL使得客户端能够通过正确的域名访问到上传的文件。
@Value("${domain.name}") @Value("${domain.name}")
private String domainName; private String domainName;
// 通过@Autowired注解注入CacheService实例CacheService应该是用于与缓存相关的操作比如操作Redis缓存等
// 在生成头像编号等逻辑中会使用它来对某个缓存键进行自增操作,以此来生成唯一的头像编号,确保头像文件命名的唯一性等情况。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
// 定义一个表示头像编号的无格式字符串常量,这里格式为"00000",用于在格式化生成头像编号时作为一种占位格式,
// 通过结合日期信息以及自增的序号等内容,生成具有特定格式的头像编号,方便对会员头像文件进行命名管理等操作。
private static final String AVATOR_NO_FORMAT = "00000"; private static final String AVATOR_NO_FORMAT = "00000";
// 定义一个头像文件名称的前缀常量,设置为"MTX",在生成头像文件完整名称时,会将这个前缀添加到前面,再结合日期、自增序号等信息,组成完整的头像文件名,便于识别和管理头像文件。
private static final String AVATOR_PREFIX = "MTX"; private static final String AVATOR_PREFIX = "MTX";
// 创建一个Logger对象用于记录日志信息通过LoggerFactory获取与当前类对应的Logger实例
// 在代码的关键位置如文件上传成功或出现异常等情况可以使用这个Logger记录相应的日志方便后续查看系统运行情况、排查问题等操作。
public Logger logger = LoggerFactory.getLogger(getClass()); public Logger logger = LoggerFactory.getLogger(getClass());
/**
* GET
* ModelAndViewHttpSessionmemberServiceModelAndView
* ModelAndViewSpring MVC
*
* @param model ModelAndView"member/account""member""account"
* addObject便
* @param session HttpSession"currMember"
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "member/account.html", method = RequestMethod.GET) @RequestMapping(value = "member/account.html", method = RequestMethod.GET)
public ModelAndView index(ModelAndView model , HttpSession session){ public ModelAndView index(ModelAndView model, HttpSession session) {
model.setViewName("member/account"); model.setViewName("member/account");
MemberEntity member = (MemberEntity) session.getAttribute("currMember"); MemberEntity member = (MemberEntity) session.getAttribute("currMember");
model.addObject("member" , memberService.findByUid(member.getId())); model.addObject("member", memberService.findByUid(member.getId()));
return model; return model;
} }
/**
* POSTJSON@RequestBodyMemberEntity
* IDShiroUtilsIDmemberServiceupdateMember
* Result便
*
* @param member MemberEntity@RequestBodyJSON
* ID
* @return ResultResult.successResult
*/
@RequestMapping(value = "member/account/update.html", method = RequestMethod.POST) @RequestMapping(value = "member/account/update.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result updateMember(@RequestBody MemberEntity member){ public Result updateMember(@RequestBody MemberEntity member) {
member.setId(ShiroUtils.getMemberId()); member.setId(ShiroUtils.getMemberId());
memberService.updateMember(member); memberService.updateMember(member);
return Result.successResult(member); return Result.successResult(member);
} }
/**
* GET使Spring MVC
* "member/password""member""password"
*
* @param model ModelAndView"member/password"便Spring MVC
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "member/password.html", method = RequestMethod.GET) @RequestMapping(value = "member/password.html", method = RequestMethod.GET)
public ModelAndView password(ModelAndView model){ public ModelAndView password(ModelAndView model) {
model.setViewName("member/password"); model.setViewName("member/password");
return model; return model;
} }
/**
* POSTJSON@RequestBodyMemberEntity
* IDShiroUtilsIDmemberServiceupdatePwd
* memberServiceResultResult
*
* @param member MemberEntity@RequestBodyJSON
* ID
* @return memberService.updatePwdResult
*/
@RequestMapping(value = "member/password/update.html", method = RequestMethod.POST) @RequestMapping(value = "member/password/update.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result updatePwd(@RequestBody MemberEntity member){ public Result updatePwd(@RequestBody MemberEntity member) {
member.setId(ShiroUtils.getMemberId()); member.setId(ShiroUtils.getMemberId());
return memberService.updatePwd(member); return memberService.updatePwd(member);
} }
/**
* POST@RequestParam"file"MultipartFile
* 访UploaderMessage
* UploaderMessage
*
* @param file MultipartFile@RequestParam
* @param request HttpServletRequest使
*
* @return UploaderMessage访
* 访
* @throws IOException I/OSpring MVC
*/
@RequestMapping(value = "/member/uploadFile.html", method = RequestMethod.POST) @RequestMapping(value = "/member/uploadFile.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public UploaderMessage uploadFileHandler(@RequestParam("file") MultipartFile file,HttpServletRequest request) throws IOException { public UploaderMessage uploadFileHandler(@RequestParam("file") MultipartFile file, HttpServletRequest request) throws IOException {
// 判断上传的文件是否为空如果不为空说明有文件需要进行上传保存操作进入后续的文件保存逻辑如果为空则直接返回表示文件为空的错误信息的UploaderMessage对象给前端。
if (!file.isEmpty()) { if (!file.isEmpty()) {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
// 根据文件存储路径fileStoragePath和当前日期格式化为"yyyyMMdd"构建文件存储的完整目录路径例如fileStoragePath可能是"/data/uploads/",结合日期后可能就是"/data/uploads/20241215"
// 这样可以按照日期对上传文件进行分类存储,方便管理和查找文件。
String path = fileStoragePath + DateUtils.format(new Date(), "yyyyMMdd"); String path = fileStoragePath + DateUtils.format(new Date(), "yyyyMMdd");
File dir = new File(path); File dir = new File(path);
// 判断文件存储目录是否存在如果不存在则通过mkdirs方法创建多级目录确保父目录不存在时也能创建成功保证后续能够将文件保存到正确的目录下。
if (!dir.exists()) { if (!dir.exists()) {
dir.mkdirs(); dir.mkdirs();
} }
// 调用getAvatorNo方法生成一个头像编号文件名的一部分结合文件的原始文件名获取后缀名部分组成完整的头像文件名例如生成的头像编号可能是"MTX20241200001"
// 如果原始文件名为"avatar.jpg",则最终的文件名就是"MTX20241200001.jpg",以此来保证文件名的唯一性以及便于识别和管理头像文件等情况。
String avatorName = this.getAvatorNo() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf(".")); String avatorName = this.getAvatorNo() + file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));
// 获取上传文件的输入流,用于后续读取文件内容并写入到服务器磁盘文件中,准备进行文件内容的复制操作。
in = file.getInputStream(); in = file.getInputStream();
// 创建一个指向服务器磁盘文件的输出流,通过构建的文件存储路径和文件名确定要写入的目标文件位置,例如"/data/uploads/20241215/MTX20241200001.jpg"
// 使得后续可以将上传文件的内容通过输出流写入到这个目标文件中,完成文件的保存操作。
out = new FileOutputStream(path + "/" + avatorName); out = new FileOutputStream(path + "/" + avatorName);
// 创建一个字节数组作为缓冲区大小为1024字节用于在读取文件流和写入文件到磁盘过程中以一定的缓冲区大小进行数据传输提高文件读写效率避免一次性读取或写入大量数据导致内存占用过大等问题。
byte[] b = new byte[1024]; byte[] b = new byte[1024];
int len = 0; int len = 0;
// 通过循环不断从输入流中读取数据到缓冲区每次最多读取缓冲区大小的数据量直到读取到文件末尾即read方法返回 -1
// 然后将缓冲区中的数据通过输出流写入到服务器磁盘文件中,实现文件内容的完整复制,完成文件上传保存操作。
while ((len = in.read(b)) > 0) { while ((len = in.read(b)) > 0) {
out.write(b, 0, len); out.write(b, 0, len);
} }
// 关闭输出流释放相关的资源确保文件写入操作完成后正确关闭流避免资源泄露等问题虽然在try-with-resources语句块中可以自动关闭流但这里按照传统的方式先手动关闭确保操作的准确性。
out.close(); out.close();
// 关闭输入流,同样是为了释放资源,完成文件读取操作后的资源清理工作,保证整个文件上传过程中资源的合理使用和正确管理。
in.close(); in.close();
// 使用Logger记录文件在服务器端保存的完整位置信息方便后续查看文件存储情况、排查文件相关问题等操作例如在出现文件丢失、访问异常等情况时可以通过日志查看文件实际保存的位置是否正确等信息。
logger.info("Server File Location=" + path + avatorName); logger.info("Server File Location=" + path + avatorName);
// 创建一个UploaderMessage对象用于封装文件上传成功的相关信息准备返回给前端告知文件上传成功以及提供文件的访问路径等信息方便前端进行相应的提示和后续处理。
UploaderMessage msg = new UploaderMessage(); UploaderMessage msg = new UploaderMessage();
msg.setStatus(Status.SUCCESS); msg.setStatus(Status.SUCCESS);
msg.setStatusMsg("File upload success"); msg.setStatusMsg("File upload success");
// 设置文件的访问路径,通过构建相对路径(以"/files/"开头结合日期和文件名的方式方便前端根据域名后续通过设置的fileDomain属性告知前端域名信息来构建完整的文件访问URL
// 例如前端可以根据返回的fileDomain如"www.example.com"和filePath如"/files/20241215/MTX20241200001.jpg")拼接出完整的文件访问链接"http://www.example.com/files/20241215/MTX20241200001.jpg"。
msg.setFilePath("/files/" + DateUtils.format(new Date(), "yyyyMMdd") + "/" + avatorName); msg.setFilePath("/files/" + DateUtils.format(new Date(), "yyyyMMdd") + "/" + avatorName);
msg.setFileDomain(domainName); msg.setFileDomain(domainName);
return msg; return msg;
} catch (Exception e) { } catch (Exception e) {
// 如果在文件上传保存过程中出现异常比如创建目录失败、文件流读写异常等情况创建一个UploaderMessage对象设置其状态为表示错误的Status.ERROR
// 并设置错误信息为"File upload file"(这里可能是代码笔误,应该是更具体准确的错误描述会更好,不过当前就是简单设置这个通用的错误提示),然后将这个表示错误信息的对象返回给前端,告知前端文件上传失败及原因。
UploaderMessage msg = new UploaderMessage(); UploaderMessage msg = new UploaderMessage();
msg.setStatus(Status.ERROR); msg.setStatus(Status.ERROR);
msg.setError("File upload file"); msg.setError("File upload file");
return msg; return msg;
} finally {
if (out != null) {
out.close();
out = null;
}
if (in != null) { private String getAvatorNo() {
in.close(); // 创建一个SimpleDateFormat对象用于将日期格式化为指定的格式这里指定的格式是"yyyyMM",即按照四位年份和两位月份的形式来格式化日期,
in = null; // 例如当前日期是2024年12月格式化后得到的字符串就是"202412",后续会基于这个格式化后的日期字符串来生成头像编号,以便在一定程度上通过日期来区分不同时间段生成的头像文件等情况。
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
// 使用创建好的SimpleDateFormat对象对当前日期通过new Date()获取当前系统时间对应的Date对象进行格式化操作得到一个表示当前年月的字符串
// 并将这个格式化后的字符串赋值给format变量用于后续构建缓存键以及生成头像编号的相关操作它体现了时间维度上的标识信息方便对头像文件进行归类和管理等操作。
String format = sdf.format(new Date());
// 创建一个DecimalFormat对象使用之前定义的常量AVATOR_NO_FORMAT值为"00000")作为格式化模式,
// 这个DecimalFormat对象主要用于将后续生成的自增序号数字类型按照指定的格式进行格式化例如将数字1格式化为"00001",保证生成的头像编号在格式上的一致性和规范性,便于识别和管理。
DecimalFormat df = new DecimalFormat(AVATOR_NO_FORMAT);
// 构建一个缓存键key字符串通过将常量AVATOR_PREFIX值为"MTX"和前面格式化得到的表示年月的format字符串进行拼接
// 例如拼接后得到的字符串可能是"MTX202412",这个缓存键会用于在缓存中进行相关操作(这里主要是对其对应的值进行自增操作,用于生成头像编号的序号部分),通过缓存可以方便地在不同请求间对头像编号进行统一管理和递增生成,避免重复等问题。
String key = AVATOR_PREFIX + format;
// 调用cacheService的incr方法传入前面构建的缓存键key这个incr方法应该是用于对缓存中指定键对应的值进行自增操作假设底层使用的是类似Redis等支持自增操作的缓存实现
// 例如第一次调用时如果缓存中该键不存在会初始化为0然后自增为1后续每次调用都会在之前的值基础上自增1返回自增后的结果以Long类型返回这个结果就作为头像编号中的序号部分用于区分同一时间段内不同的头像文件。
Long incr = cacheService.incr(key);
// 使用之前创建的DecimalFormat对象df对自增后的序号incr进行格式化操作将其格式化为符合要求的字符串形式按照"00000"的格式进行填充如数字1会格式化为"00001"等),
// 然后将常量AVATOR_PREFIX"MTX")与格式化后的序号字符串进行拼接,组成完整的头像编号字符串,例如得到的头像编号可能是"MTX20241200001",最终返回这个生成的头像编号,供其他方法使用来命名头像文件等操作。
String avatorNo = AVATOR_PREFIX + df.format(incr);
return avatorNo;
} }
}
} else {
UploaderMessage msg = new UploaderMessage();
msg.setStatus(Status.ERROR);
msg.setError("File is empty");
return msg;
}
}
private String getAvatorNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
String format = sdf.format(new Date());
DecimalFormat df = new DecimalFormat(AVATOR_NO_FORMAT);
String key = AVATOR_PREFIX + format;
Long incr = cacheService.incr(key);
String avatorNo = AVATOR_PREFIX + df.format(incr);
return avatorNo;
}
}

@ -22,94 +22,158 @@ import com.tamguo.common.utils.Result;
import com.tamguo.common.utils.SystemConstant; import com.tamguo.common.utils.SystemConstant;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与用户登录相关的各种请求,如生成验证码、登录验证、判断登录状态等操作。
@Controller @Controller
public class LoginController { public class LoginController {
/**
* "captcha.jpg"访HttpSession
* 便"image/jpeg"JPEG
*
* @param response HttpServletResponse
* @param session HttpSessionSystemConstant.KAPTCHA_SESSION_KEY便
* @throws ServletException ServletServlet
* @throws IOException I/O
*/
@RequestMapping("captcha.jpg") @RequestMapping("captcha.jpg")
public void captcha(HttpServletResponse response , HttpSession session) throws ServletException, IOException { public void captcha(HttpServletResponse response, HttpSession session) throws ServletException, IOException {
// 设置响应头的"Cache-Control"字段,值为"no-store, no-cache",表示禁止浏览器缓存响应内容,这样每次客户端请求这个验证码图片时,都会获取到新生成的验证码图片,
// 避免因缓存导致验证码重复使用等安全问题,确保验证码的时效性和安全性。
response.setHeader("Cache-Control", "no-store, no-cache"); response.setHeader("Cache-Control", "no-store, no-cache");
// 设置响应的内容类型为"image/jpeg"告知客户端返回的数据是JPEG格式的图片使得客户端能够正确识别并展示接收到的验证码图片内容。
response.setContentType("image/jpeg"); response.setContentType("image/jpeg");
// 调用CaptchaUtils工具类的generateCaptcha方法传入响应的输出流response.getOutputStream()),用于生成验证码图片并将图片数据写入到输出流中,返回生成的验证码字符串,
// 这个方法内部可能使用了一些图形处理库等实现了验证码图片的生成以及将验证码文本以图片形式输出到流中的功能具体实现取决于CaptchaUtils类的代码逻辑。
String a = CaptchaUtils.generateCaptcha(response.getOutputStream()); String a = CaptchaUtils.generateCaptcha(response.getOutputStream());
// 将生成的验证码字符串存储到HttpSession中使用SystemConstant.KAPTCHA_SESSION_KEY作为键方便后续在登录验证等操作中从会话中获取该验证码字符串进行对比验证确保用户输入的验证码是否正确。
session.setAttribute(SystemConstant.KAPTCHA_SESSION_KEY, a); session.setAttribute(SystemConstant.KAPTCHA_SESSION_KEY, a);
} }
/**
* GET使Spring MVC
* "login""login""isVerifyCode""0"
*
*
* @param model ModelAndView"login""isVerifyCode"便
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "/login.html", method = RequestMethod.GET) @RequestMapping(value = "/login.html", method = RequestMethod.GET)
public ModelAndView login(ModelAndView model){ public ModelAndView login(ModelAndView model) {
model.setViewName("login"); model.setViewName("login");
model.addObject("isVerifyCode" , "0"); model.addObject("isVerifyCode", "0");
return model; return model;
} }
/**
* POST
* Result
* HttpSessionShiroResult
*
* @param username Shiro
* @param password Shiro
* @param verifyCode HttpSession
* @param model ModelAndView便
* @param session HttpSession"currMember"便
* @param response HttpServletResponse"member/index.html"
* @return ModelAndViewSpring MVCnull
* @throws IOException response.sendRedirectI/O
*/
@RequestMapping(value = "/submitLogin.html", method = RequestMethod.POST) @RequestMapping(value = "/submitLogin.html", method = RequestMethod.POST)
public ModelAndView submitLogin(String username , String password , String verifyCode , ModelAndView model , HttpSession session , HttpServletResponse response) throws IOException{ public ModelAndView submitLogin(String username, String password, String verifyCode, ModelAndView model, HttpSession session, HttpServletResponse response) throws IOException {
// 创建一个表示成功的Result对象初始化为成功状态这里虽然初始化为成功状态但后续会根据实际验证情况进行修改用于封装登录验证的结果信息方便返回给前端展示相应的提示内容
// 一开始假设登录验证是成功的后续根据具体的验证逻辑来更新这个Result对象的状态、提示信息等属性。
Result result = Result.successResult(null); Result result = Result.successResult(null);
if(StringUtils.isEmpty(verifyCode)) {
// 判断用户输入的验证码是否为空如果为空则表示用户没有输入验证码设置Result对象的状态码、提示信息等属性
// 表示验证失败提示用户需要输入验证码然后将这个包含错误提示信息的Result对象相关信息设置到ModelAndView中最终返回登录页面展示错误提示给用户。
if (StringUtils.isEmpty(verifyCode)) {
result = Result.result(202, null, "请输入验证码"); result = Result.result(202, null, "请输入验证码");
} else if(StringUtils.isNotEmpty(verifyCode)){ } else if (StringUtils.isNotEmpty(verifyCode)) {
// 如果用户输入的验证码不为空则从HttpSession中获取之前生成并存储的验证码字符串通过SystemConstant.KAPTCHA_SESSION_KEY作为键获取
// 然后将获取到的验证码字符串与用户输入的验证码进行不区分大小写的对比验证,判断验证码是否正确。
String kaptcha = session.getAttribute(SystemConstant.KAPTCHA_SESSION_KEY).toString(); String kaptcha = session.getAttribute(SystemConstant.KAPTCHA_SESSION_KEY).toString();
if (!verifyCode.equalsIgnoreCase(kaptcha)) { if (!verifyCode.equalsIgnoreCase(kaptcha)) {
result = Result.result(205, null, "验证码错误"); result = Result.result(205, null, "验证码错误");
} else { } else {
// 如果验证码验证通过通过ShiroUtils工具类获取当前的Subject对象在Shiro框架中Subject代表了当前与系统进行交互的“主体”通常就是指当前登录的用户或者一个匿名的访问者未登录时
// 获取到Subject对象后用于后续进行用户认证相关的操作比如传递用户名和密码进行登录验证等操作。
Subject subject = ShiroUtils.getSubject(); Subject subject = ShiroUtils.getSubject();
// 创建一个UsernamePasswordToken对象用于传递用户名和密码信息给Shiro框架进行认证操作将用户输入的用户名和密码作为参数传入构造方法
// 这个对象会作为参数传递给Subject的login方法触发Shiro框架的认证流程去验证用户名和密码是否匹配以及用户是否合法等情况。
UsernamePasswordToken token = new UsernamePasswordToken(username, password); UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try { try {
// 调用Subject的login方法传入前面创建的UsernamePasswordToken对象触发Shiro框架的用户认证流程
// 如果认证成功即用户名和密码正确且用户状态正常等情况则继续执行后续的操作如果认证失败会抛出相应的异常如UnknownAccountException、IncorrectCredentialsException等进入对应的catch块进行相应的错误处理。
subject.login(token); subject.login(token);
// 如果登录认证通过通过ShiroUtils工具类获取当前登录用户对应的MemberEntity对象假设认证时将MemberEntity类型的用户对象设置为了Principal
// 然后将这个会员对象存储到HttpSession的"currMember"属性下,方便后续在其他请求处理中获取当前登录用户的信息进行相关业务操作,例如在页面上展示用户相关信息等情况。
session.setAttribute("currMember", ShiroUtils.getMember()); session.setAttribute("currMember", ShiroUtils.getMember());
// 使用HttpServletResponse进行重定向操作将用户重定向到会员相关的首页这里是"member/index.html"),实现登录成功后的页面跳转,使得用户登录后能够进入到对应的会员页面进行操作,
// 重定向后方法直接返回null因为已经完成了页面跳转操作不需要再返回ModelAndView对象让Spring MVC进行视图渲染了后续的请求会根据新的页面路径重新进行相应的请求处理
response.sendRedirect("member/index.html"); response.sendRedirect("member/index.html");
return null; return null;
} catch (UnknownAccountException e) { } catch (UnknownAccountException e) {
// 如果在Shiro认证过程中抛出UnknownAccountException异常说明用户名不存在或者没有找到对应的用户信息设置Result对象的状态码、提示信息等属性
// 表示验证失败提示用户用户名或密码有误让用户重新输入或找回密码然后将这个包含错误提示信息的Result对象相关信息设置到ModelAndView中最终返回登录页面展示错误提示给用户。
result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码"); result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码");
} catch (IncorrectCredentialsException e) { } catch (IncorrectCredentialsException e) {
// 如果抛出IncorrectCredentialsException异常意味着密码不正确同样设置Result对象的相应属性提示用户用户名或密码有误让用户重新输入或找回密码
// 并将包含错误提示信息的Result对象信息设置到ModelAndView中返回登录页面展示错误提示给用户。
result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码"); result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码");
} catch (LockedAccountException e) { } catch (LockedAccountException e) {
// 如果出现LockedAccountException异常表示用户账号被锁定了设置Result对象的属性提示用户账号被锁定的错误信息
// 再将包含该提示信息的Result对象相关内容设置到ModelAndView中返回登录页面展示错误提示给用户。
result = Result.result(203, null, "账号被锁定"); result = Result.result(203, null, "账号被锁定");
} }
} }
} }
model.setViewName("login");
// 如果登录验证没有通过由于验证码错误、用户名或密码错误、账号被锁定等原因设置ModelAndView的视图名称为"login",表示返回登录页面展示错误提示信息给用户,
// 然后将Result对象中的状态码、提示信息以及用户输入的用户名等信息添加到ModelAndView对象中方便在登录页面上展示相应的提示内容以及回显用户输入的用户名等情况方便用户重新输入登录信息进行再次尝试。
model.setViewName("login");
model.addObject("code", result.getCode()); model.addObject("code", result.getCode());
model.addObject("msg" , result.getMessage()); model.addObject("msg", result.getMessage());
model.addObject("username", username); model.addObject("username", username);
return model; return model;
} }
@RequestMapping(value = "/miniLogin.html", method = RequestMethod.GET)
@ResponseBody
public Result miniLogin(String username , String password , String captcha, ModelAndView model , HttpSession session) {
Result result = null;
if(StringUtils.isEmpty(captcha)) {
result = Result.result(204, null, "请输入验证码");
} else if(StringUtils.isNotEmpty(captcha)){
String kaptcha = session.getAttribute(SystemConstant.KAPTCHA_SESSION_KEY).toString();
if (!captcha.equalsIgnoreCase(kaptcha)) {
result = Result.result(205, null, "验证码错误");
}else {
Subject subject = ShiroUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
session.setAttribute("currMember", ShiroUtils.getMember());
result = Result.successResult(ShiroUtils.getMember());
} catch (UnknownAccountException e) {
result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码");
} catch (IncorrectCredentialsException e) {
result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码");
} catch (LockedAccountException e) {
result = Result.result(203, null, "账号被锁定");
}
}
}
return result;
}
/**
* GETsubmitLogin
* Result@ResponseBodyResultJSONAjax
* submitLoginModelAndViewShiro
*
* @param username Shiro
* @param password Shiro
* @param captcha HttpSession
* @param model ModelAndView@ResponseBody
* @param session HttpSession"currMember"便
* @return Result@ResponseBodyResultJSON
*
*/
/**
* GET"/isLogin.html"
* ShiroUtilsisLoginResult
*
*
* @return ResultResult1"已经登录"Result0"未登录"
* @ResponseBodyResultJSON便
*/
@RequestMapping(value = "/isLogin.html", method = RequestMethod.GET) @RequestMapping(value = "/isLogin.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result isLogin() { public Result isLogin() {
if(ShiroUtils.isLogin()) { // 调用ShiroUtils工具类的isLogin方法该方法会检查当前用户对应的Subject对象中的Principal是否为null
return Result.result(1, null , "已经登录"); // 如果不为null则表示用户已经登录即已通过认证有对应的用户主体信息存在如果为null则表示当前是匿名访问或者未进行认证操作用户未登录。
if (ShiroUtils.isLogin()) {
// 如果用户已登录调用Result类的result方法创建一个Result对象传入状态码1、提示信息"已经登录",表示登录状态为已登录,
// 后续这个Result对象会被返回给前端告知前端用户当前已登录的情况以便前端进行相应的页面展示或逻辑处理。
return Result.result(1, null, "已经登录");
} }
// 如果用户未登录同样调用Result类的result方法创建一个Result对象传入状态码0、提示信息"未登录",表示登录状态为未登录,
// 最后将这个表示未登录的Result对象返回给前端方便前端根据该结果进行相应的处理比如显示登录按钮引导用户进行登录操作等情况。
return Result.result(0, null, "未登录"); return Result.result(0, null, "未登录");
} }
}

@ -10,19 +10,38 @@ import org.springframework.web.bind.annotation.RequestMethod;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求在这里这个控制器主要用于处理用户注销相关的请求操作实现用户登出的功能。
@Controller @Controller
public class LogoutController { public class LogoutController {
/** /**
* * GET"logout.html"访
* HttpSession"currMember"ShiroUtilslogoutShiro
* "/"使
*
* @param request HttpServletRequest
* 使
* @param response HttpServletResponse
* @param session HttpSession"currMember"
* ShiroUtilsShiro
* @return "redirect:/"Spring MVC
* 使便
*/ */
@RequestMapping(value = "logout.html", method = RequestMethod.GET) @RequestMapping(value = "logout.html", method = RequestMethod.GET)
public String logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) { public String logout(HttpServletRequest request, HttpServletResponse response, HttpSession session) {
if (session.getAttribute("currMember") != null) { // 判断HttpSession中是否存在名为"currMember"的属性,通常在用户登录成功后,会将会员相关信息存储在这个属性下,
// 如果该属性不为null说明用户当前处于登录状态需要进行注销相关的操作进入下面的注销逻辑处理如果为null则可能用户已经处于未登录状态或者其他异常情况不过当前代码暂不做额外处理直接返回重定向指令。
if (session.getAttribute("currMember")!= null) {
// 从HttpSession中移除名为"currMember"的属性,清除存储在会话中的当前会员相关信息,例如清除会员对象本身以及一些基于会员登录状态存储的临时业务数据等,
// 完成会话层面上与当前会员相关的数据清理工作,确保注销后会话中不再保留会员相关的痕迹,保障数据的安全性和系统状态的一致性。
session.removeAttribute("currMember"); session.removeAttribute("currMember");
// 调用ShiroUtils工具类的logout方法该方法会通知Shiro框架执行用户登出操作清除Shiro框架中与当前用户相关的认证状态、会话信息等相关数据
// 例如撤销权限缓存、清除Shiro会话中的相关属性等操作将用户的登录状态置为未登录释放与登录相关的资源确保在Shiro框架层面完成用户的注销流程保证系统的安全性和稳定性。
ShiroUtils.logout(); ShiroUtils.logout();
} }
// 返回"redirect:/"字符串这个字符串在Spring MVC中会被解析为一个重定向的指令会使得客户端收到一个重定向响应跳转到网站的根路径"/"
// 一般情况下根路径对应的是网站的首页或者其他公共的起始页面,通过这样的重定向操作,让用户在注销后回到相应的页面,方便后续的浏览或者重新登录等操作。
return "redirect:/"; return "redirect:/";
} }
}
}

@ -11,22 +11,44 @@ import com.tamguo.common.utils.Result;
import com.tamguo.modules.member.service.IMemberService; import com.tamguo.modules.member.service.IMemberService;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与会员相关的页面展示及信息查询等操作。
@Controller @Controller
public class MemberController { public class MemberController {
// 通过@Autowired注解自动注入IMemberService接口的实现类实例IMemberService用于处理与会员相关的各种业务逻辑
// 比如根据会员ID查询会员详细信息等操作在这个控制器的多个方法中会调用其相关方法来实现具体的会员业务功能。
@Autowired @Autowired
IMemberService iMemberService; IMemberService iMemberService;
/**
* "/member/index.html"GET
* ModelAndViewiMemberServicefindByUidShiroUtilsID
* ModelAndViewModelAndViewSpring MVC
*
* @param model ModelAndView"member/index""member""index"
* addObject便
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "/member/index.html", method = RequestMethod.GET) @RequestMapping(value = "/member/index.html", method = RequestMethod.GET)
public ModelAndView index(ModelAndView model){ public ModelAndView index(ModelAndView model) {
model.setViewName("member/index"); model.setViewName("member/index");
model.addObject("member" , iMemberService.findByUid(ShiroUtils.getMember().getId())); model.addObject("member", iMemberService.findByUid(ShiroUtils.getMember().getId()));
return model; return model;
} }
/**
* GET"/member/findCurrMember.html"Result
* iMemberServicefindCurrMemberShiroUtilsID使Result.successResultResult
* @ResponseBodyResultJSON便
*
* @return ResultResult.successResultiMemberService
*
*/
@RequestMapping(value = "/member/findCurrMember.html", method = RequestMethod.GET) @RequestMapping(value = "/member/findCurrMember.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result findCurrMember() { public Result findCurrMember() {
return Result.successResult(iMemberService.findCurrMember(ShiroUtils.getMemberId())); return Result.successResult(iMemberService.findCurrMember(ShiroUtils.getMemberId()));
} }
} }

@ -21,107 +21,157 @@ import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpSession; import javax.servlet.http.HttpSession;
import java.util.Map; import java.util.Map;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理会员相关试卷的各种操作,如试卷列表展示、试卷信息查询、试卷及试卷题目信息的增删改等业务功能。
@Controller @Controller
public class MemberPaperController { public class MemberPaperController {
// 通过@Autowired注解自动注入IPaperService接口的实现类实例IPaperService用于处理与试卷相关的各种业务逻辑
// 比如查询试卷信息、添加试卷、修改试卷、删除试卷以及处理试卷题目相关信息等操作,在这个控制器的各个方法中会调用其相关方法来实现具体的试卷业务功能。
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
/**
* "/member/paper.html"GET使Spring MVC
* "member/paperList""member""paperList"
*
* @param model ModelAndView"member/paperList"便Spring MVC
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "/member/paper.html", method = RequestMethod.GET) @RequestMapping(value = "/member/paper.html", method = RequestMethod.GET)
public ModelAndView paper(ModelAndView model){ public ModelAndView paper(ModelAndView model) {
model.setViewName("member/paperList"); model.setViewName("member/paperList");
return model; return model;
} }
/**
* GETIDpaperIdResult
* iPaperServiceselectByIdID使Result.successResultResult
* @ResponseBodyResultJSON便
*
* @param paperId iPaperServiceselectById
* @return ResultResult.successResultiPaperServiceID
*/
@RequestMapping(value = "/member/findPaper.html", method = RequestMethod.GET) @RequestMapping(value = "/member/findPaper.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result findPaper(String paperId) { public Result findPaper(String paperId) {
return Result.successResult(iPaperService.selectById(paperId)); return Result.successResult(iPaperService.selectById(paperId));
} }
@SuppressWarnings({ "unchecked"}) /**
@RequestMapping(value = "member/paper/list.html" , method = RequestMethod.GET) * GETMap
* HttpSession
*
* @param name MyBatis PlusConditionlike
* @param page Page便
* @param limit Pagepage
* @param session HttpSession"currMember"MemberEntityID
*
* @return Map<String, Object>Result.jqGridResult
* Map
*/
@SuppressWarnings({ "unchecked" })
@RequestMapping(value = "member/paper/list.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Map<String, Object> paperList(String name , Integer page , Integer limit , HttpSession session){ public Map<String, Object> paperList(String name, Integer page, Integer limit, HttpSession session) {
MemberEntity member = ((MemberEntity)session.getAttribute("currMember")); // 从HttpSession中获取当前登录会员的信息通过将名为"currMember"的属性强制转换为MemberEntity类型获取会员对象用于后续获取会员ID作为查询条件
Page<PaperEntity> list = iPaperService.selectPage(new Page<PaperEntity>(page , limit) , Condition.create().like("name", name).eq("creater_id", member.getId())); // 筛选出当前会员创建的试卷列表信息,确保数据的关联性和权限控制(只展示当前会员创建的试卷)。
MemberEntity member = ((MemberEntity) session.getAttribute("currMember"));
// 创建一个Page对象用于进行分页查询操作传入当前页码page和每页显示的记录数量limit作为分页参数指定要查询的分页情况
// 同时通过Condition.create().like("name", name).eq("creater_id", member.getId())构建查询条件使用MyBatis Plus的条件构造器
// like("name", name)表示按照试卷名称进行模糊查询包含传入的name参数值的试卷eq("creater_id", member.getId())表示筛选出创建者ID等于当前会员ID的试卷
// 最终通过iPaperService的selectPage方法进行分页查询获取符合条件的试卷列表信息以Page<PaperEntity>形式返回,包含了分页相关的数据以及试卷记录列表等内容)。
Page<PaperEntity> list = iPaperService.selectPage(new Page<PaperEntity>(page, limit),
Condition.create().like("name", name).eq("creater_id", member.getId()));
// 使用Result.jqGridResult方法将分页查询得到的试卷列表相关信息试卷记录、总记录数、每页记录数、当前页码、总页数等按照特定格式封装到一个Map中返回给前端
// 方便前端根据这个Map中的数据进行试卷列表的展示以及分页相关的操作处理例如在页面上循环展示试卷列表信息并根据总页数等信息实现分页导航等功能。
return Result.jqGridResult(list.getRecords(), list.getTotal(), limit, page, list.getPages()); return Result.jqGridResult(list.getRecords(), list.getTotal(), limit, page, list.getPages());
} }
@RequestMapping(value="member/paperList/addPaperQuestionInfo.html",method=RequestMethod.POST) /**
* POSTJSON@RequestBodyJSONObject
* JSONIDiPaperServiceaddPaperQuestionInfo
* ResultExceptionSupportResult
*
* @param data JSONObject@RequestBodyJSONID
*
* @return ResultResult0"修改成功""添加成功"
* ExceptionSupportresolverResultResult
*/
@RequestMapping(value = "member/paperList/addPaperQuestionInfo.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result addPaperQuestionInfo(@RequestBody JSONObject data){ public Result addPaperQuestionInfo(@RequestBody JSONObject data) {
try { try {
String paperId ; String title ; String name ;String type; // 从传入的JSONObject数据中获取试卷ID字符串用于指定要添加题目信息的目标试卷该ID对应数据库中试卷表的唯一标识字段确保题目信息添加到正确的试卷上。
String paperId;
String title;
String name;
String type;
paperId = data.getString("id"); paperId = data.getString("id");
// 获取题目标题字符串,作为题目信息的关键内容之一,传递给服务层用于添加到试卷题目相关的表中,展示题目的具体内容。
title = data.getString("title"); title = data.getString("title");
// 获取题目类型字符串,用于后续根据题目类型获取对应的描述信息以及区分不同类型的题目进行不同的处理逻辑(比如不同类型题目可能在数据库中的存储结构等有所不同)。
type = data.getString("type"); type = data.getString("type");
// 根据获取到的题目类型type通过QuestionTypeEnum枚举类的getQuestionType方法获取对应的枚举实例再调用其getDesc方法获取该类型题目的描述信息
// 例如将题目类型代码转换为对应的中文描述等情况方便在试卷题目展示等方面提供更友好的信息然后将这个描述信息赋值给name变量用于后续添加题目信息操作中传递给服务层。
name = QuestionTypeEnum.getQuestionType(type).getDesc(); name = QuestionTypeEnum.getQuestionType(type).getDesc();
iPaperService.addPaperQuestionInfo(paperId , title , name , type); // 调用iPaperService的addPaperQuestionInfo方法传入试卷ID、题目标题、题目描述信息、题目类型等参数将题目信息添加到对应的试卷中实现添加试卷题目信息的业务逻辑操作。
iPaperService.addPaperQuestionInfo(paperId, title, name, type);
return Result.result(0, null, "修改成功"); return Result.result(0, null, "修改成功");
} catch (Exception e) { } catch (Exception e) {
// 如果在添加试卷题目信息过程中出现异常通过ExceptionSupport工具类的resolverResult方法处理异常传入操作名称"添加questionInfo",用于在异常信息中标识是哪个操作出现了问题)、
// 当前类的Class对象用于定位异常出现的位置等信息以及捕获到的异常对象e该方法会根据异常情况生成一个包含相应错误信息的Result对象返回给前端告知前端添加操作失败及失败原因等情况方便前端进行相应的提示和后续处理操作。
return ExceptionSupport.resolverResult("添加questionInfo", this.getClass(), e); return ExceptionSupport.resolverResult("添加questionInfo", this.getClass(), e);
} }
} }
/**
* POSTaddPaperQuestionInfoJSON@RequestBodyJSONObject
* IDiPaperServiceupdatePaperQuestionInfo
* ResultExceptionSupportResult
*
* @param data JSONObject@RequestBodyJSONID
*
* @return ResultResult0"修改成功"
* ExceptionSupportresolverResultResult
*/
@RequestMapping("member/paperList/updatePaperQuestionInfo.html") @RequestMapping("member/paperList/updatePaperQuestionInfo.html")
@ResponseBody @ResponseBody
public Result updatePaperQuestionInfo(@RequestBody JSONObject data){ public Result updatePaperQuestionInfo(@RequestBody JSONObject data) {
try { try {
String paperId ; String title ; String name ; String type ; String uid; // 从传入的JSONObject数据中获取试卷ID字符串用于指定要修改题目信息的目标试卷确保更新操作作用在正确的试卷对应的题目上。
String paperId;
String title;
String name;
String type;
String uid;
paperId = data.getString("uid"); paperId = data.getString("uid");
// 获取题目标题字符串,作为要修改的题目关键内容之一,传递给服务层用于更新数据库中对应的题目记录的标题字段等内容。
title = data.getString("title"); title = data.getString("title");
// 获取题目类型字符串,用于后续根据题目类型获取对应的描述信息以及按照不同类型题目进行相应的更新逻辑处理(比如不同类型题目可能更新的字段等有所不同)。
type = data.getString("type"); type = data.getString("type");
// 根据获取到的题目类型type通过QuestionTypeEnum枚举类的getQuestionType方法获取对应的枚举实例再调用其getDesc方法获取该类型题目的描述信息
// 以便在更新题目信息时使用更友好的描述内容替换原有的信息等情况然后将这个描述信息赋值给name变量传递给服务层进行更新操作。
name = QuestionTypeEnum.getQuestionType(type).getDesc(); name = QuestionTypeEnum.getQuestionType(type).getDesc();
// 获取题目信息的唯一标识字符串(可能用于在数据库中精准定位要更新的具体题目记录,比如是数据库表中的主键等唯一标识字段),传递给服务层在更新操作中确定具体要修改的题目记录。
uid = data.getString("infoUid"); uid = data.getString("infoUid");
iPaperService.updatePaperQuestionInfo(paperId , title , name , type , uid); // 调用iPaperService的updatePaperQuestionInfo方法传入试卷ID、题目标题、题目描述信息、题目类型、题目信息唯一标识等参数更新对应的试卷题目信息实现修改试卷题目信息的业务逻辑操作。
iPaperService.updatePaperQuestionInfo(paperId, title, name, type, uid);
return Result.result(0, null, "修改成功"); return Result.result(0, null, "修改成功");
} catch (Exception e) { } catch (Exception e) {
// 如果在修改试卷题目信息过程中出现异常通过ExceptionSupport工具类的resolverResult方法处理异常传入操作名称"修改questionInfo",用于在异常信息中标识是哪个操作出现了问题)、
// 当前类的Class对象用于定位异常出现的位置等信息以及捕获到的异常对象e该方法会根据异常情况生成一个包含相应错误信息的Result对象返回给前端告知前端修改操作失败及失败原因等情况方便前端进行相应的提示和后续处理操作。
return ExceptionSupport.resolverResult("修改questionInfo", this.getClass(), e); return ExceptionSupport.resolverResult("修改questionInfo", this.getClass(), e);
} }
} }
@RequestMapping("member/paperList/deletePaper") /**
@ResponseBody * IDpaperIdIDShiroUtilsiPaperServicedeletePaper
public Result deletePaper(String paperId){ * iPaperServiceResultExceptionSupportResult
try { *
return iPaperService.deletePaper(ShiroUtils.getMemberId() , paperId); * @param paperId iPaperServicedeletePaper
} catch (Exception e) { * @return ResultiPaperService.deletePaperResult
return ExceptionSupport.resolverResult("删除试卷", this.getClass(), e); * ExceptionSupportresolverResultResult
} */
} @RequestMapping("member/paperList/deletePaper
@RequestMapping("member/paperList/deletePaperQuestionInfoBtn.html")
@ResponseBody
public Result deletePaperQuestionInfoBtn(String paperId , String uid){
try {
return iPaperService.deletePaperQuestionInfoBtn(ShiroUtils.getMemberId() , paperId , uid);
} catch (Exception e) {
return ExceptionSupport.resolverResult("删除子卷", this.getClass(), e);
}
}
@RequestMapping(value="member/paperList/addPaper.html",method=RequestMethod.POST)
@ResponseBody
public Result addPaper(@RequestBody PaperEntity paper,HttpSession session){
try {
MemberEntity member = (MemberEntity) session.getAttribute("currMember");
paper.setCreaterId(member.getId());
iPaperService.addPaper(paper);
return Result.result(Result.SUCCESS_CODE, paper, "添加成功");
} catch (Exception e) {
return ExceptionSupport.resolverResult("添加试卷", this.getClass(), e);
}
}
@RequestMapping(value="member/paperList/updatePaper.html",method=RequestMethod.POST)
@ResponseBody
public Result updatePaper(@RequestBody PaperEntity paper){
try {
return iPaperService.updatePaper(paper , ShiroUtils.getMemberId());
} catch (Exception e) {
return ExceptionSupport.resolverResult("修改试卷", this.getClass(), e);
}
}
}

@ -12,70 +12,146 @@ import com.tamguo.common.utils.Result;
import com.tamguo.common.utils.SystemConstant; import com.tamguo.common.utils.SystemConstant;
import com.tamguo.modules.member.service.IMemberService; import com.tamguo.modules.member.service.IMemberService;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与用户密码相关的各种操作,比如密码找回过程中涉及的账户确认、安全验证、密码重置等业务功能对应的页面请求及处理逻辑。
@Controller @Controller
public class PasswordController { public class PasswordController {
// 通过@Autowired注解自动注入IMemberService接口的实现类实例IMemberService用于处理与会员相关的各种业务逻辑
// 在密码相关操作中比如确认账户是否存在、进行安全验证、重置密码等操作时会调用其相关方法来实现具体的业务功能根据不同的业务逻辑返回相应的Result对象来表示操作结果情况。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/**
* "password/find.html"GET使Spring MVC
* "password/confirmAccount""password""confirmAccount"
*
* @param model ModelAndView"password/confirmAccount"便Spring MVC
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "password/find.html", method = RequestMethod.GET) @RequestMapping(value = "password/find.html", method = RequestMethod.GET)
public ModelAndView confirmAccount(ModelAndView model){ public ModelAndView confirmAccount(ModelAndView model) {
model.setViewName("password/confirmAccount"); model.setViewName("password/confirmAccount");
return model; return model;
} }
/**
* POSTusernameveritycode
* iMemberServiceconfirmAccountHttpSessionModelAndView
* ModelAndViewSpring MVC
*
* @param username iMemberServiceconfirmAccount
* @param veritycode HttpSession
* @param model ModelAndView便
* @param session HttpSessionSystemConstant.KAPTCHA_SESSION_KEY
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "password/confirmAccount.html", method = RequestMethod.POST) @RequestMapping(value = "password/confirmAccount.html", method = RequestMethod.POST)
public ModelAndView submitConfirmAccount(String username , String veritycode , ModelAndView model , HttpSession session){ public ModelAndView submitConfirmAccount(String username, String veritycode, ModelAndView model, HttpSession session) {
// 调用iMemberService的confirmAccount方法传入用户输入的用户名和验证码信息该方法会根据业务逻辑验证账户是否存在、验证码是否匹配等情况具体验证逻辑由IMemberService的实现类决定
// 返回一个Result对象其中包含了验证结果的相关信息如状态码表示验证是否成功等情况、提示信息等内容后续会根据这个Result对象以及验证码的对比情况来进一步处理页面跳转和数据传递等操作。
Result result = iMemberService.confirmAccount(username, veritycode); Result result = iMemberService.confirmAccount(username, veritycode);
// 从HttpSession中获取之前存储的验证码字符串通过SystemConstant.KAPTCHA_SESSION_KEY作为键获取将获取到的验证码字符串与用户输入的验证码进行不区分大小写的对比验证判断验证码是否正确。
String kaptcha = session.getAttribute(SystemConstant.KAPTCHA_SESSION_KEY).toString(); String kaptcha = session.getAttribute(SystemConstant.KAPTCHA_SESSION_KEY).toString();
if (!veritycode.equalsIgnoreCase(kaptcha)) { if (!veritycode.equalsIgnoreCase(kaptcha)) {
// 如果验证码对比不一致说明验证码错误创建一个Result对象并设置其状态码为202提示信息为"验证码错误",表示验证失败,
// 后续会根据这个表示验证码错误的Result对象来设置ModelAndView相关信息最终返回确认账户页面展示错误提示给用户。
result = Result.result(202, null, "验证码错误"); result = Result.result(202, null, "验证码错误");
} }
if(result.getCode() == 200){
// 判断Result对象中的状态码是否为200通常表示验证成功等情况具体含义由业务中Result类的定义以及服务层返回逻辑决定
// 如果状态码为200说明账户验证通过包括验证码也正确等情况则设置ModelAndView的视图名称为"password/securityCheck",表示要跳转到密码找回的安全验证页面,
// 同时将Result对象以及根据用户名判断是否为邮箱得到的标识信息"1"表示是邮箱,"0"表示不是邮箱添加到ModelAndView对象中方便在安全验证页面展示相应的验证结果以及相关提示信息给用户。
if (result.getCode() == 200) {
model.setViewName("password/securityCheck"); model.setViewName("password/securityCheck");
model.addObject("result", result); model.addObject("result", result);
model.addObject("isEmail", username.contains("@") ? "1" : "0"); model.addObject("isEmail", username.contains("@")? "1" : "0");
}else{ } else {
// 如果Result对象的状态码不是200说明账户验证失败可能是账户不存在、其他验证规则不满足等原因设置ModelAndView的视图名称为"password/confirmAccount",表示返回确认账户页面展示错误提示信息给用户,
// 然后将用户输入的账户信息、用户名、验证码以及Result对象中的状态码等相关信息添加到ModelAndView对象中方便在确认账户页面上展示相应的提示内容以及回显用户输入的信息等情况方便用户重新输入进行再次尝试。
model.setViewName("password/confirmAccount"); model.setViewName("password/confirmAccount");
model.addObject("account", username); model.addObject("account", username);
model.addObject("username",username); model.addObject("username", username);
model.addObject("veritycode", veritycode); model.addObject("veritycode", veritycode);
model.addObject("code", result.getCode()); model.addObject("code", result.getCode());
} }
return model; return model;
} }
/**
* "password/securityCheck.html"POST
* iMemberServicesecurityCheckResult
*
* @param username iMemberServicesecurityCheck
* @param isEmail "1""0"iMemberServicesecurityCheck
* @param mobileVcode iMemberServicesecurityCheck
* @param model ModelAndView便
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "password/securityCheck.html", method = RequestMethod.POST) @RequestMapping(value = "password/securityCheck.html", method = RequestMethod.POST)
public ModelAndView securityCheck(String username , String isEmail , String mobileVcode , ModelAndView model){ public ModelAndView securityCheck(String username, String isEmail, String mobileVcode, ModelAndView model) {
Result result = iMemberService.securityCheck(username , isEmail , mobileVcode); // 调用iMemberService的securityCheck方法传入用户名、是否为邮箱标识信息以及手机验证码等参数该方法会根据业务逻辑进行安全验证操作比如验证手机验证码是否正确、结合账户信息判断是否符合安全要求等情况
if(result.getCode() == 200){ // 返回一个Result对象其中包含了安全验证结果的相关信息如状态码表示验证是否成功等情况、提示信息以及可能的其他验证相关结果数据等内容后续会根据这个Result对象来进一步处理页面跳转和数据传递等操作。
Result result = iMemberService.securityCheck(username, isEmail, mobileVcode);
// 判断Result对象中的状态码是否为200通常表示安全验证成功等情况具体含义由业务中Result类的定义以及服务层返回逻辑决定
// 如果状态码为200说明安全验证通过将用户名以及iMemberService的securityCheck方法返回的用于密码重置的关键信息通过result.getResult()获取可能是一个重置密码的令牌等关键数据添加到ModelAndView对象中
// 同时设置ModelAndView的视图名称为"password/resetPassword",表示要跳转到密码重置页面,方便用户在该页面进行密码重置操作,使用获取到的关键信息来完成重置流程。
if (result.getCode() == 200) {
model.addObject("username", username); model.addObject("username", username);
model.addObject("resetPasswordKey" , result.getResult()); model.addObject("resetPasswordKey", result.getResult());
model.setViewName("password/resetPassword"); model.setViewName("password/resetPassword");
}else{ } else {
// 如果Result对象的状态码不是200说明安全验证失败将Result对象、是否为邮箱标识信息以及一个表示验证码错误的标识"1"这里可能根据具体业务逻辑有更准确的含义比如表示验证失败相关情况添加到ModelAndView对象中
// 同时设置ModelAndView的视图名称为"password/securityCheck",表示返回安全验证页面展示错误提示信息给用户,方便用户查看错误原因并重新进行安全验证操作等情况。
model.addObject("result", result); model.addObject("result", result);
model.addObject("isEmail", isEmail); model.addObject("isEmail", isEmail);
model.addObject("codeError", "1"); model.addObject("codeError", "1");
model.setViewName("password/securityCheck"); model.setViewName("password/securityCheck");
} }
return model; return model;
} }
/**
* "password/resetPassword.html"POSTiMemberServiceresetPasswordResult
* 便
*
* @param resetPasswordKey iMemberServiceresetPassword
* @param username iMemberServiceresetPassword
* @param password iMemberServiceresetPassword
* @param verifypwd iMemberServiceresetPassword
* @param model ModelAndView便
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "password/resetPassword.html", method = RequestMethod.POST) @RequestMapping(value = "password/resetPassword.html", method = RequestMethod.POST)
public ModelAndView resetPassword(String resetPasswordKey , String username , String password , String verifypwd , ModelAndView model){ public ModelAndView resetPassword(String resetPasswordKey, String username, String password, String verifypwd, ModelAndView model) {
Result result = iMemberService.resetPassword(resetPasswordKey , username , password , verifypwd); // 调用iMemberService的resetPassword方法传入重置密码关键信息、用户名、新密码以及验证新密码的信息等参数该方法会根据业务逻辑进行密码重置操作比如验证关键信息是否有效、新密码格式是否正确、两次输入密码是否一致等情况
if(result.getCode() == 200){ // 返回一个Result对象其中包含了密码重置结果的相关信息如状态码表示重置是否成功等情况、提示信息等内容后续会根据这个Result对象来进一步处理页面跳转和数据传递等操作。
Result result = iMemberService.resetPassword(resetPasswordKey, username, password, verifypwd);
// 判断Result对象中的状态码是否为200通常表示密码重置成功等情况具体含义由业务中Result类的定义以及服务层返回逻辑决定
// 如果状态码为200说明密码重置成功设置ModelAndView的视图名称为"password/resetPwSuccess",表示要跳转到密码重置成功页面,展示相应的成功提示信息给用户,告知用户密码重置操作已顺利完成。
if (result.getCode() == 200) {
model.setViewName("password/resetPwSuccess"); model.setViewName("password/resetPwSuccess");
}else{ } else {
// 如果Result对象的状态码不是200说明密码重置失败设置ModelAndView的视图名称为"password/resetPassword"表示返回密码重置页面方便用户查看可能的错误提示信息由服务层返回的Result对象中的提示信息决定
// 然后重新输入密码等信息进行再次尝试密码重置操作。
model.setViewName("password/resetPassword"); model.setViewName("password/resetPassword");
} }
return model; return model;
} }
@RequestMapping(value = "password/checkAccount.html", method = RequestMethod.GET) /**
@ResponseBody * GET"password/checkAccount.html"accountiMemberServicecheckAccount
public Result checkAccount(String account){ * ResultResult
return iMemberService.checkAccount(account); *
} * @param account iMemberServicecheckAccountResult
* @return ResultiMemberServicecheckAccountResult@ResponseBodyResultJSON
} *
*/
@RequestMapping(value = "password/checkAccount.html", method = RequestMethod

@ -11,20 +11,46 @@ import com.tamguo.modules.tiku.model.QuestionAnswerEntity;
import com.tamguo.modules.tiku.service.IQuestionAnswerService; import com.tamguo.modules.tiku.service.IQuestionAnswerService;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与会员答题相关的业务操作,具体为保存会员答题信息的功能。
@Controller @Controller
public class QuestionAnswerController { public class QuestionAnswerController {
// 通过@Autowired注解自动注入IQuestionAnswerService接口的实现类实例IQuestionAnswerService用于处理与题目答案相关的各种业务逻辑
// 比如保存用户提交的答题信息到数据库等操作在这个控制器的sendAnswer方法中会调用其相关方法来实现具体的保存答题信息的业务功能。
@Autowired @Autowired
private IQuestionAnswerService iQuestionAnswerService; private IQuestionAnswerService iQuestionAnswerService;
/**
* POST"member/sendAnswer.html"QuestionAnswerEntity@RequestBodyJSON
* IDShiroUtilsiQuestionAnswerServicesendAnswer
* Result便
*
* @param entity QuestionAnswerEntity@RequestBodyIDQuestionAnswerEntity
* ID
* @return ResultResult.result0"保存成功"
*
*/
@RequestMapping(value = "member/sendAnswer.html", method = RequestMethod.POST) @RequestMapping(value = "member/sendAnswer.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result sendAnswer(@RequestBody QuestionAnswerEntity entity){ public Result sendAnswer(@RequestBody QuestionAnswerEntity entity) {
// 通过ShiroUtils工具类获取当前登录会员对应的实体对象并从中获取会员头像信息赋值给传入的QuestionAnswerEntity对象的memberAvatar属性
// 这样在保存答题信息时,能够关联上当前答题会员的头像信息,方便后续在展示答题记录等场景中显示答题会员的头像情况。
entity.setMemberAvatar(ShiroUtils.getMember().getAvatar()); entity.setMemberAvatar(ShiroUtils.getMember().getAvatar());
// 通过ShiroUtils工具类获取当前登录会员的ID并赋值给QuestionAnswerEntity对象的memberId属性用于明确答题信息所属的会员
// 使得数据库等数据源中能够准确记录是哪个会员提交的该答题信息,方便进行数据关联查询以及权限管理等相关业务操作(例如只有会员本人或特定权限角色才能查看、修改其答题信息等情况)。
entity.setMemberId(ShiroUtils.getMemberId()); entity.setMemberId(ShiroUtils.getMemberId());
// 通过ShiroUtils工具类获取当前登录会员对应的实体对象并从中获取会员昵称信息赋值给QuestionAnswerEntity对象的memberName属性
// 同样在保存答题信息时关联上会员昵称,便于后续展示答题记录等场景中更友好地显示答题会员的相关信息,提高信息展示的可读性和用户体验。
entity.setMemberName(ShiroUtils.getMember().getNickName()); entity.setMemberName(ShiroUtils.getMember().getNickName());
// 调用iQuestionAnswerService的sendAnswer方法传入完善了会员相关属性的QuestionAnswerEntity对象将答题信息保存到数据库等数据源中执行具体的答题信息保存业务逻辑操作
// 比如将答题信息插入到对应的答题记录表中,同时可能还涉及到一些关联表的更新操作(如更新会员答题次数统计等相关表,具体取决于业务逻辑设计)。
iQuestionAnswerService.sendAnswer(entity); iQuestionAnswerService.sendAnswer(entity);
// 返回一个Result对象通过Result.result方法创建传入状态码0、提示信息"保存成功"以及null表示无其他额外返回数据不过这里可根据业务需求调整
// 告知前端答题信息保存操作已成功完成,方便前端根据这个返回结果进行相应的提示和后续处理,例如在页面上显示答题成功的提示信息给用户,或者根据业务逻辑进行页面跳转等操作。
return Result.result(0, null, "保存成功"); return Result.result(0, null, "保存成功");
} }
}
}

@ -18,80 +18,164 @@ import com.tamguo.modules.tiku.service.IPaperService;
import com.tamguo.modules.tiku.service.IQuestionService; import com.tamguo.modules.tiku.service.IQuestionService;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
@Controller(value="memberQuestionController") // @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件并且指定了组件的名称为"memberQuestionController"
// Spring容器会在启动时扫描到这个类并进行管理使得类中的方法能够处理对应的HTTP请求根据请求的映射关系调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理会员相关试题的各种操作,例如添加试题、查询试题列表、编辑和更新试题以及删除试题等业务功能对应的页面请求及处理逻辑。
@Controller(value = "memberQuestionController")
public class QuestionController { public class QuestionController {
// 通过@Autowired注解自动注入IQuestionService接口的实现类实例IQuestionService用于处理与试题相关的各种业务逻辑
// 比如添加试题、查询试题、更新试题、删除试题等具体操作,在这个控制器的多个方法中会调用其相关方法来实现具体的试题业务功能。
@Autowired @Autowired
private IQuestionService iQuestionService; private IQuestionService iQuestionService;
// 通过@Autowired注解自动注入IPaperService接口的实现类实例IPaperService主要用于处理与试卷相关的业务逻辑
// 在涉及试题与试卷关联的操作中例如根据试卷ID查询试卷信息、获取属于某个试卷的试题列表等情况会调用其相关方法来获取试卷相关的数据辅助试题相关业务操作的完成。
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
/**
* "/member/addQuestion.html"GETIDpaperIdModelAndView
* 使便ModelAndViewSpring MVC
*
* @param paperId iPaperServiceselectById
* 便
* @param model ModelAndView"member/addQuestion""member""addQuestion"
* 便
* @return ModelAndViewSpring MVC使
*/
@RequestMapping(value = "/member/addQuestion.html", method = RequestMethod.GET) @RequestMapping(value = "/member/addQuestion.html", method = RequestMethod.GET)
public ModelAndView index(String paperId , ModelAndView model){ public ModelAndView index(String paperId, ModelAndView model) {
model.setViewName("member/addQuestion"); model.setViewName("member/addQuestion");
model.addObject("paper", iPaperService.selectById(paperId)); model.addObject("paper", iPaperService.selectById(paperId));
return model; return model;
} }
/**
* POSTQuestionEntityiQuestionServiceaddQuestion
* iQuestionServiceResultExceptionSupportResult
*
* @param question QuestionEntityQuestionEntityiQuestionServiceaddQuestion
* IDShiroUtils
* @return ResultiQuestionService.addQuestionResult
* ExceptionSupportresolverResultResult
*/
@RequestMapping(value = "/member/submitQuestion.html", method = RequestMethod.POST) @RequestMapping(value = "/member/submitQuestion.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result submitQuestion(QuestionEntity question){ public Result submitQuestion(QuestionEntity question) {
try { try {
return iQuestionService.addQuestion(question , ShiroUtils.getMemberId()); return iQuestionService.addQuestion(question, ShiroUtils.getMemberId());
} catch (Exception e) { } catch (Exception e) {
return ExceptionSupport.resolverResult("添加试题", this.getClass(), e); return ExceptionSupport.resolverResult("添加试题", this.getClass(), e);
} }
} }
/**
* "/member/questionList.html"GETIDpaperIdModelAndView"member/questionList"
* 使Spring MVC
* ModelAndView
*
* @param paperId iPaperServiceselectByIdModelAndView
* 便
* @param model ModelAndView"member/questionList"
* 便Spring MVC使
* @return ModelAndViewSpring MVC便
*/
@RequestMapping(value = "/member/questionList.html", method = RequestMethod.GET) @RequestMapping(value = "/member/questionList.html", method = RequestMethod.GET)
public ModelAndView questionList(String paperId , ModelAndView model){ public ModelAndView questionList(String paperId, ModelAndView model) {
model.addObject("paper", iPaperService.selectById(paperId)); model.addObject("paper", iPaperService.selectById(paperId));
model.setViewName("member/questionList"); model.setViewName("member/questionList");
return model; return model;
} }
@RequestMapping(value = "/member/queryQuestionList.html" , method=RequestMethod.POST) /**
* POSTJSONObject@RequestBodyJSONJSONID
* iQuestionServicequeryQuestionListMap
*
* @param data JSONObject@RequestBodyJSONID
*
* @return Map<String, Object>Result.jqGridResult
* Map
*/
@RequestMapping(value = "/member/queryQuestionList.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Map<String, Object> queryQuestionList(@RequestBody JSONObject data){ public Map<String, Object> queryQuestionList(@RequestBody JSONObject data) {
String questionType ; String uid ; String content ; String paperId ; // 从传入的JSONObject数据中获取试题类型字符串用于筛选特定类型的试题作为查询条件之一传递给iQuestionService的queryQuestionList方法
Integer page ; Integer limit; // 以便获取符合该类型要求的试题列表信息,例如只查询选择题、填空题等不同类型的试题,具体取决于业务中对试题类型的分类和使用方式。
String questionType;
String uid;
String content;
String paperId;
Integer page;
Integer limit;
questionType = data.getString("questionType"); questionType = data.getString("questionType");
// 获取试题的唯一标识字符串,可用于精准定位特定的试题(比如在更新、删除或者更精确的查询场景下作为筛选条件),传递给查询方法用于在查询过程中进一步筛选或关联特定试题记录。
uid = data.getString("uid"); uid = data.getString("uid");
content = data.getString("content"); // 获取试题内容字符串,作为模糊查询的条件之一,用于查找包含该内容的试题(例如通过题目中包含的关键字进行搜索等情况),传递给服务层在查询操作中进行相应的模糊查询逻辑处理。
content = data.getString("questionType");
// 获取试卷ID字符串用于筛选属于该试卷的试题确保查询到的试题列表是与指定试卷相关的传递给服务层结合其他条件进行试题的筛选查询操作获取对应试卷下的试题信息。
paperId = data.getString("paperId"); paperId = data.getString("paperId");
// 获取当前页码整数用于指定要查询的页码信息传递给Page对象来确定查询的分页情况获取对应页码的数据记录方便实现分页展示试题列表的功能。
page = data.getInteger("page"); page = data.getInteger("page");
// 获取每页显示的记录数量整数同样传递给Page对象来设置每页显示的试题数量结合page参数实现分页查询和展示试题列表的功能。
limit = data.getInteger("limit"); limit = data.getInteger("limit");
// 创建一个Page对象用于进行分页查询操作通过设置当前页码page和每页显示的记录数量limit来确定分页情况
// 后续将这个Page对象以及其他查询条件一起传递给iQuestionService的queryQuestionList方法进行分页查询符合条件的试题列表信息。
Page<QuestionEntity> p = new Page<>(); Page<QuestionEntity> p = new Page<>();
p.setCurrent(page); p.setCurrent(page);
p.setSize(limit); p.setSize(limit);
Page<QuestionEntity> list = iQuestionService.queryQuestionList(questionType , uid , content , paperId , ShiroUtils.getMemberId() , p);
// 调用iQuestionService的queryQuestionList方法传入试题类型、试题唯一标识、试题内容、试卷ID、当前登录会员的ID通过ShiroUtils获取可能用于权限控制等情况确保会员只能查询自己有权限查看的试题以及构建好的Page对象作为参数
// 进行分页查询操作获取符合条件的试题列表信息以Page<QuestionEntity>形式返回,包含了分页相关的数据以及试题记录列表等内容)。
Page<QuestionEntity> list = iQuestionService.queryQuestionList(questionType, uid, content, paperId, ShiroUtils.getMemberId(), p);
// 使用Result.jqGridResult方法将分页查询得到的试题列表相关信息试题记录、总记录数、每页记录数、当前页码、总页数等按照特定格式封装到一个Map中返回给前端
// 方便前端根据这个Map中的数据进行试题列表的展示以及分页相关的操作处理例如在页面上循环展示试题列表信息并根据总页数等信息实现分页导航等功能。
return Result.jqGridResult(list.getRecords(), list.getTotal(), limit, page, list.getPages()); return Result.jqGridResult(list.getRecords(), list.getTotal(), limit, page, list.getPages());
} }
/**
* "/member/editQuestion.html"GETIDpaperIdIDquestionIdIDModelAndView
* 使ModelAndViewSpring MVC
*
* @param paperId iPaperServiceselectById
* 便
* @param questionId ModelAndView便
* ID
* @param model ModelAndView"member/editQuestion""member""editQuestion"
* ID便
* @return IDModelAndViewSpring MVC使
*/
@RequestMapping(value = "/member/editQuestion.html", method = RequestMethod.GET) @RequestMapping(value = "/member/editQuestion.html", method = RequestMethod.GET)
public ModelAndView editQuestion(String paperId , String questionId , ModelAndView model){ public ModelAndView editQuestion(String paperId, String questionId, ModelAndView model) {
model.setViewName("member/editQuestion"); model.setViewName("member/editQuestion");
model.addObject("paper", iPaperService.selectById(paperId)); model.addObject("paper", iPaperService.selectById(paperId));
model.addObject("questionId" , questionId); model.addObject("questionId", questionId);
return model; return model;
} }
/**
* GETIDquestionIdResult
* iQuestionServiceselectByIdID使Result.successResultResult
* @ResponseBodyResultJSON便
*
* @param questionId iQuestionServiceselectById
* @return ResultResult.successResultiQuestionServiceID
*/
@RequestMapping(value = "/member/getQuestion.html", method = RequestMethod.GET) @RequestMapping(value = "/member/getQuestion.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result getQuestion(String questionId) { public Result getQuestion(String questionId) {
return Result.successResult(iQuestionService.selectById(questionId)); return Result.successResult(iQuestionService.selectById(questionId));
} }
@RequestMapping(value = "/member/updateQuestion.html", method = RequestMethod.POST) /**
@ResponseBody * POSTQuestionEntityiQuestionServiceupdateQuestion
public Result updateQuestion(QuestionEntity question) { * iQuestionServiceResult
return iQuestionService.updateQuestion(question , ShiroUtils.getMemberId()); *
} * @param question QuestionEntityiQuestionServiceupdateQuestion
* IDShiroUtils
* @return ResultiQuestionService.updateQuestionResult
@RequestMapping(value = "/member/deleteQuestion.html", method = RequestMethod.GET) *
@ResponseBody */
public Result deleteQuestion(@RequestBody String uid) { @RequestMapping(value = "/member/updateQuestion.html", method = RequestMethod.POST)
return iQuestionService.delete(uid , ShiroUtils.getMemberId());
}
}

@ -19,51 +19,116 @@ import com.tamguo.modules.member.model.MemberEntity;
import com.tamguo.modules.member.service.IMemberService; import com.tamguo.modules.member.service.IMemberService;
import com.tamguo.utils.ShiroUtils; import com.tamguo.utils.ShiroUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与用户注册相关的各种操作,比如注册页面展示、注册信息验证以及实际的用户注册和注册后尝试登录等业务功能。
@Controller @Controller
public class RegisterController { public class RegisterController {
// 通过@Autowired注解自动注入IMemberService接口的实现类实例IMemberService用于处理与会员相关的各种业务逻辑
// 在注册相关操作中比如验证用户名是否可用、验证手机号是否可用、进行用户注册等操作时会调用其相关方法来实现具体的业务功能根据不同的业务逻辑返回相应的Result对象来表示操作结果情况。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/**
* "/register.html"GET使Spring MVC
* "register""register"
*
* @param model ModelAndView"register"便Spring MVC
* @param session HttpSession
*
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "/register.html", method = RequestMethod.GET) @RequestMapping(value = "/register.html", method = RequestMethod.GET)
public ModelAndView register(ModelAndView model , HttpSession session) { public ModelAndView register(ModelAndView model, HttpSession session) {
model.setViewName("register"); model.setViewName("register");
return model; return model;
} }
/**
* GET"/checkUsername.html"username使iMemberServicecheckUsername
* ResultResult
*
* @param username iMemberServicecheckUsernameResult
* @return ResultiMemberServicecheckUsernameResult@ResponseBodyResultJSON
*
*/
@RequestMapping(value = "/checkUsername.html", method = RequestMethod.GET) @RequestMapping(value = "/checkUsername.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result checkUsername(String username){ public Result checkUsername(String username) {
return iMemberService.checkUsername(username); return iMemberService.checkUsername(username);
} }
/**
* GET"/checkMobile.html"mobileiMemberServicecheckMobile
* ResultResult
*
* @param mobile iMemberServicecheckMobileResult
* @return ResultiMemberServicecheckMobileResult@ResponseBodyResultJSON
*
*/
@RequestMapping(value = "/checkMobile.html", method = RequestMethod.GET) @RequestMapping(value = "/checkMobile.html", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result checkMobile(String mobile){ public Result checkMobile(String mobile) {
return iMemberService.checkMobile(mobile); return iMemberService.checkMobile(mobile);
} }
/**
* POSTMemberEntity@RequestBodyJSON
* iMemberServiceregisterResult200使Shiro
* ResultResult200Result
*
* @param member MemberEntity@RequestBodyMemberEntity
* iMemberServiceregister
* @param session HttpSession"currMember"便
* @return ResultResultiMemberServiceregister
* iMemberServiceregisterResult
* Result便
*/
@RequestMapping(value = "/subRegister.html", method = RequestMethod.POST) @RequestMapping(value = "/subRegister.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result subRegister(@RequestBody MemberEntity member , HttpSession session){ public Result subRegister(@RequestBody MemberEntity member, HttpSession session) {
// 调用iMemberService的register方法传入包含用户注册信息的MemberEntity对象该方法会根据业务逻辑进行用户注册操作比如将用户信息插入到数据库的用户表中进行合法性验证、唯一性验证等相关操作
// 返回一个Result对象其中包含了注册操作的结果相关信息如状态码表示注册是否成功等情况、提示信息以及可能的注册成功后返回的用户相关数据等内容后续会根据这个Result对象的状态码来进一步处理登录相关逻辑以及最终返回结果给前端的操作。
Result result = iMemberService.register(member); Result result = iMemberService.register(member);
if(result.getCode() == 200) {
// 判断Result对象中的状态码是否为200通常表示注册成功等情况具体含义由业务中Result类的定义以及服务层返回逻辑决定
// 如果状态码为200说明注册成功接下来尝试进行登录操作以下是登录相关的逻辑处理。
if (result.getCode() == 200) {
// 通过ShiroUtils工具类获取当前的Subject对象在Shiro框架中Subject代表了当前与系统进行交互的“主体”通常就是指当前登录的用户或者一个匿名的访问者未登录时
// 获取到Subject对象后用于后续进行用户认证相关的操作也就是使用刚注册的用户信息进行登录验证操作。
Subject subject = ShiroUtils.getSubject(); Subject subject = ShiroUtils.getSubject();
// 从注册操作返回的Result对象中获取注册成功后的用户实体对象通过强制转换为MemberEntity类型获取假设注册成功时会将创建的用户对象作为结果返回在Result中
// 用于后续创建UsernamePasswordToken对象传递用户名和密码信息进行登录操作。
MemberEntity memberEntity = (MemberEntity) result.getResult(); MemberEntity memberEntity = (MemberEntity) result.getResult();
// 创建一个UsernamePasswordToken对象用于传递用户名和密码信息给Shiro框架进行认证操作将注册成功的用户的用户名memberEntity.getUsername()和注册时填写的密码member.getPassword())作为参数传入构造方法,
// 这个对象会作为参数传递给Subject的login方法触发Shiro框架的认证流程去验证用户名和密码是否匹配以及用户是否合法等情况。
UsernamePasswordToken token = new UsernamePasswordToken(memberEntity.getUsername(), member.getPassword()); UsernamePasswordToken token = new UsernamePasswordToken(memberEntity.getUsername(), member.getPassword());
try { try {
// 调用Subject的login方法传入前面创建的UsernamePasswordToken对象触发Shiro框架的用户认证流程
// 如果认证成功即用户名和密码正确且用户状态正常等情况则继续执行后续的操作如果认证失败会抛出相应的异常如UnknownAccountException、IncorrectCredentialsException等进入对应的catch块进行相应的错误处理。
subject.login(token); subject.login(token);
// 如果登录认证通过通过ShiroUtils工具类获取当前登录用户对应的MemberEntity对象假设认证时将MemberEntity类型的用户对象设置为了Principal
// 然后将这个会员对象存储到HttpSession的"currMember"属性下,方便后续在其他请求处理中获取当前登录用户的信息进行相关业务操作,例如在页面上展示用户相关信息等情况。
session.setAttribute("currMember", ShiroUtils.getMember()); session.setAttribute("currMember", ShiroUtils.getMember());
} catch (UnknownAccountException e) { } catch (UnknownAccountException e) {
// 如果在Shiro认证过程中抛出UnknownAccountException异常说明用户名不存在或者没有找到对应的用户信息返回一个Result对象设置其状态码为201提示信息为"用户名或密码有误,请重新输入或找回密码"
// 表示登录失败将这个Result对象返回给前端告知前端登录失败的原因方便前端进行相应的提示和后续处理操作。
return Result.result(201, null, "用户名或密码有误,请重新输入或找回密码"); return Result.result(201, null, "用户名或密码有误,请重新输入或找回密码");
} catch (IncorrectCredentialsException e) { } catch (IncorrectCredentialsException e) {
// 如果抛出IncorrectCredentialsException异常意味着密码不正确返回一个Result对象设置其状态码为202提示信息为"用户名或密码有误,请重新输入或找回密码"
// 同样将这个Result对象返回给前端告知前端登录失败是因为密码错误方便前端进行相应的提示和后续处理操作。
return Result.result(202, null, "用户名或密码有误,请重新输入或找回密码"); return Result.result(202, null, "用户名或密码有误,请重新输入或找回密码");
} catch (LockedAccountException e) { } catch (LockedAccountException e) {
// 如果出现LockedAccountException异常表示用户账号被锁定了返回一个Result对象设置其状态码为203提示信息为"账号被锁定"
// 并将这个Result对象返回给前端告知前端登录失败是因为账号被锁定的原因方便前端进行相应的提示和后续处理操作。
return Result.result(203, null, "账号被锁定"); return Result.result(203, null, "账号被锁定");
} }
} }
// 如果注册操作不成功Result对象的状态码不为200或者注册成功但登录过程中出现异常在对应的catch块中已经返回相应的Result对象直接将最初注册操作返回的Result对象返回给前端
// 这个Result对象中包含了注册失败的原因等相关信息方便前端根据返回结果进行相应的提示和后续处理操作比如提示用户注册失败的具体原因如用户名已存在、手机号格式错误等情况
return result; return result;
} }
}
}

@ -15,31 +15,77 @@ import com.tamguo.common.utils.Result;
import com.tamguo.modules.sys.model.TeacherEntity; import com.tamguo.modules.sys.model.TeacherEntity;
import com.tamguo.modules.sys.service.ITeacherService; import com.tamguo.modules.sys.service.ITeacherService;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与教师应聘相关的各种操作,比如应聘页面展示、获取教师信息以及提交教师应聘信息等业务功能。
@Controller @Controller
public class JoinusController { public class JoinusController {
// 通过@Autowired注解自动注入ITeacherService接口的实现类实例ITeacherService用于处理与教师相关的各种业务逻辑
// 在教师应聘相关操作中比如根据手机号获取教师信息、保存教师应聘信息等操作时会调用其相关方法来实现具体的业务功能根据不同的业务逻辑返回相应的Result对象来表示操作结果情况。
@Autowired @Autowired
private ITeacherService iTeacherService; private ITeacherService iTeacherService;
/**
* "teacher/joinus.html"GET使Spring MVC
* "teacher/joinus""teacher""joinus"
*
* @param model ModelAndView"teacher/joinus"便Spring MVC
* @param session HttpSession访
*
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "teacher/joinus.html", method = RequestMethod.GET) @RequestMapping(value = "teacher/joinus.html", method = RequestMethod.GET)
public ModelAndView register(ModelAndView model , HttpSession session) { public ModelAndView register(ModelAndView model, HttpSession session) {
model.setViewName("teacher/joinus"); model.setViewName("teacher/joinus");
return model; return model;
} }
/**
* POST"teacher/info.html"Map@RequestBodyJSONMapJSONMap
* MapmobileverifyCodeiTeacherServicegetTeacherByMobileResult
* Result
*
* @param param Map<String, Object>@RequestBodyMap
* Map
* @return ResultiTeacherServicegetTeacherByMobileResult@ResponseBodyResultJSON
*
*/
@RequestMapping(value = "teacher/info.html", method = RequestMethod.POST) @RequestMapping(value = "teacher/info.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result getTeacher(@RequestBody Map<String, Object> param) { public Result getTeacher(@RequestBody Map<String, Object> param) {
// 从传入的Map参数中获取手机号字符串作为查询教师信息的关键条件之一传递给iTeacherService的getTeacherByMobile方法用于在数据库等数据源中查找对应的教师记录
// 例如通过手机号来精准定位特定教师的相关信息判断是否存在该手机号对应的教师以及验证手机号与验证码是否匹配等情况具体逻辑由ITeacherService的实现类决定
String mobile = (String) param.get("mobile"); String mobile = (String) param.get("mobile");
// 从Map参数中获取验证码字符串同样作为重要的查询验证条件与手机号一起传递给iTeacherService的getTeacherByMobile方法用于验证教师的合法性或者确认当前操作的有效性等情况
// 确保只有输入正确验证码的情况下才能获取相应教师的信息,保证信息获取的安全性和准确性。
String verifyCode = (String) param.get("verifyCode"); String verifyCode = (String) param.get("verifyCode");
// 调用iTeacherService的getTeacherByMobile方法传入手机号和验证码作为参数该方法会根据业务逻辑进行教师信息查询以及相关验证操作返回一个Result对象
// 其中包含了查询及验证结果的相关信息如状态码表示是否查询到教师信息、验证是否通过等情况、提示信息以及可能的查询到的教师实体对象等内容后续会将这个Result对象直接返回给前端告知前端教师信息获取的结果情况。
Result result = iTeacherService.getTeacherByMobile(mobile, verifyCode); Result result = iTeacherService.getTeacherByMobile(mobile, verifyCode);
return result; return result;
} }
/**
* POSTTeacherEntity@RequestBodyJSON
* iTeacherServicejoinusResultResult.successResult便
*
* @param teacher TeacherEntity@RequestBodyTeacherEntity
* iTeacherServicejoinus
* @return ResultResult.successResultnull
*
*/
@RequestMapping(value = "teacher/joinus.html", method = RequestMethod.POST) @RequestMapping(value = "teacher/joinus.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result teacherJoinus(@RequestBody TeacherEntity teacher) { public Result teacherJoinus(@RequestBody TeacherEntity teacher) {
// 调用iTeacherService的joinus方法传入包含教师应聘信息的TeacherEntity对象该方法会根据业务逻辑进行教师应聘信息的保存操作比如将教师信息插入到数据库的教师应聘表或者相关关联表中进行合法性验证等相关操作
// 完成将教师应聘信息持久化的业务逻辑,确保信息被正确保存到后端存储中,方便后续的审核等业务流程处理。
iTeacherService.joinus(teacher); iTeacherService.joinus(teacher);
// 返回一个Result对象通过Result.successResult方法创建传入null参数表示无额外需要返回的数据不过可以根据实际业务需求进行调整比如返回保存后的教师信息ID等内容
// 告知前端教师应聘信息提交操作已成功完成,方便前端根据这个返回结果进行相应的提示和后续处理,例如在页面上显示应聘成功提交的提示信息给教师,或者根据业务逻辑进行页面跳转等操作。
return Result.successResult(null); return Result.successResult(null);
} }
} }

@ -9,17 +9,28 @@ import org.springframework.web.bind.annotation.ResponseBody;
import com.tamguo.common.utils.Result; import com.tamguo.common.utils.Result;
import com.tamguo.modules.sys.service.ISysAreaService; import com.tamguo.modules.sys.service.ISysAreaService;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与区域相关的业务操作,当前类中仅实现了查询区域树信息这一功能对应的请求处理逻辑。
@Controller @Controller
public class AreaController { public class AreaController {
// 通过@Autowired注解自动注入ISysAreaService接口的实现类实例ISysAreaService用于处理与系统区域相关的各种业务逻辑
// 比如查询区域树结构数据等操作在这个控制器的findAreaTree方法中会调用其相关方法来实现具体的查询区域树信息的业务功能。
@Autowired @Autowired
private ISysAreaService iSysAreaService; private ISysAreaService iSysAreaService;
/**
* GET"area/findAreaTree.html"
* iSysAreaServicefindAreaTreeResultResult
*
*
* @return ResultiSysAreaServicefindAreaTreeResult@ResponseBodyResultJSON
*
*/
@RequestMapping(value = {"area/findAreaTree.html"}, method = RequestMethod.GET) @RequestMapping(value = {"area/findAreaTree.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result findAreaTree() { public Result findAreaTree() {
return iSysAreaService.findAreaTree(); return iSysAreaService.findAreaTree();
} }
}
}

@ -27,57 +27,129 @@ import com.tamguo.utils.BrowserUtils;
/** /**
* Controller - * Controller -
* * Spring MVCHTTP
*
*
*
* @author tamguo * @author tamguo
* *
*/ */
@Controller @Controller
public class CourseController { public class CourseController {
// 创建一个日志记录器对象,用于记录在这个控制器类中发生的一些关键操作、调试信息或者错误信息等,
// 通过LoggerFactory根据当前类的Class对象获取对应的Logger实例方便后续在代码中进行日志输出操作便于调试和查看运行情况。
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
// 通过@Autowired注解自动注入IChapterService接口的实现类实例IChapterService用于处理与章节相关的各种业务逻辑
// 比如查询章节树结构、根据课程ID获取章节信息等操作在这个控制器的相关方法中会调用其相关方法来实现具体的章节相关业务功能。
@Autowired @Autowired
IChapterService iChapterService; IChapterService iChapterService;
// 自动注入ICourseService接口的实现类实例ICourseService主要负责处理与课程相关的业务逻辑
// 像根据课程ID查询课程信息、获取某个学科下的课程列表等操作都会调用它的相应方法来完成。
@Autowired @Autowired
ICourseService iCourseService; ICourseService iCourseService;
// 注入ISubjectService接口的实现类实例ISubjectService用于处理与学科科目相关的业务逻辑
// 例如根据学科ID查询学科详细信息等操作在课程相关操作中可能需要获取所属学科的相关信息来完善页面展示等功能会用到该服务层接口的方法。
@Autowired @Autowired
ISubjectService iSubjectService; ISubjectService iSubjectService;
// 注入IKnowPointService接口的实现类实例IKnowPointService用于处理知识点相关的业务逻辑
// 比如根据课程ID查询对应的知识点列表等操作在构建课程相关页面数据时会涉及到知识点相关信息的获取与处理会调用其相应方法。
@Autowired @Autowired
IKnowPointService knowPointService; IKnowPointService knowPointService;
/**
* GET"course/{uid}.html"{uid}
*
* 访ModelAndViewSpring MVC
*
* @param request HttpServletRequestURIrequest.getRequestURI()
* "user-agent"BrowserUtils访
* @param uid @PathVariable
* @param model ModelAndView
* Spring MVC
* @return ModelAndViewSpring MVC
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"course/{uid}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"course/{uid}.html"}, method = RequestMethod.GET)
public ModelAndView index(HttpServletRequest request , @PathVariable String uid , ModelAndView model) { public ModelAndView index(HttpServletRequest request, @PathVariable String uid, ModelAndView model) {
// request url // 使用日志记录器记录请求的URL信息方便后续查看请求访问情况以及进行调试等操作通过格式化输出的方式将请求的URI信息记录到日志中
logger.info("request url :{}" , request.getRequestURI()); // 输出的日志级别为INFO通常用于记录一些重要的运行时信息方便运维人员或者开发人员了解系统的访问情况。
logger.info("request url :{}", request.getRequestURI());
// 调用iCourseService的selectById方法传入课程的唯一标识uid作为参数从数据库等数据源中查询对应的课程详细信息以CourseEntity对象形式返回
// 获取到的课程信息后续会添加到ModelAndView对象中传递给视图进行展示例如展示课程名称、课程简介等相关内容。
CourseEntity course = iCourseService.selectById(uid); CourseEntity course = iCourseService.selectById(uid);
// 调用knowPointService的selectList方法通过MyBatis Plus的条件构造器Condition创建查询条件
// 使用eq("course_id", uid)表示筛选出课程ID等于当前传入课程唯一标识uid的知识点列表获取与该课程相关的所有知识点信息以List<KnowPointEntity>形式返回),
// 这些知识点信息同样会添加到ModelAndView对象中方便在视图中展示课程包含的知识点情况比如在页面上列出知识点名称等相关内容。
List<KnowPointEntity> knowPointList = knowPointService.selectList(Condition.create().eq("course_id", uid)); List<KnowPointEntity> knowPointList = knowPointService.selectList(Condition.create().eq("course_id", uid));
// 初始化章节列表为null后续会根据知识点情况来确定章节列表的值如果有对应的知识点存在会进一步查询相关章节信息进行赋值。
List<ChapterEntity> chapterList = null; List<ChapterEntity> chapterList = null;
KnowPointEntity knowPoint = null; KnowPointEntity knowPoint = null;
if(knowPointList.size() > 0) {
// 判断知识点列表的大小是否大于0即是否存在与当前课程相关的知识点
// 如果存在知识点,说明可以基于知识点进一步获取与之关联的章节信息等内容,进行后续的数据准备操作。
if (knowPointList.size() > 0) {
// 从知识点列表中获取第一个知识点对象(这里假设可以基于第一个知识点来获取相关章节信息,具体业务逻辑可能根据实际情况而定),
// 获取这个知识点对象主要是为了获取其ID作为查询章节树结构的依据获取与该知识点相关联的章节信息。
knowPoint = knowPointList.get(0); knowPoint = knowPointList.get(0);
// 调用iChapterService的findChapterTree方法传入获取到的知识点的ID作为参数查询与该知识点相关的章节树结构信息以List<ChapterEntity>形式返回),
// 章节树结构信息可能是一种层级嵌套的数据结构方便在页面上以树状形式展示章节的层级关系等情况获取到的章节列表信息会赋值给chapterList变量后续添加到ModelAndView对象中用于视图展示。
chapterList = iChapterService.findChapterTree(knowPoint.getId()); chapterList = iChapterService.findChapterTree(knowPoint.getId());
} }
// 调用iSubjectService的selectById方法传入课程对象中的学科IDcourse.getSubjectId()作为参数从数据库等数据源中查询对应的学科详细信息以SubjectEntity对象形式返回
// 获取学科信息是为了在课程详情页面展示该课程所属的学科相关情况例如学科名称等内容将学科信息添加到ModelAndView对象中传递给视图进行展示。
SubjectEntity subject = iSubjectService.selectById(course.getSubjectId()); SubjectEntity subject = iSubjectService.selectById(course.getSubjectId());
// 调用iCourseService的selectList方法通过MyBatis Plus的条件构造器构建查询条件
// 使用eq("subject_id", course.getSubjectId())表示筛选出学科ID等于当前课程所属学科ID的课程列表再通过orderAsc(Arrays.asList("sort"))按照"sort"字段升序排序课程列表,
// 获取与当前课程同属一个学科的其他课程列表信息以List<CourseEntity>形式返回用于在课程详情页面展示同学科下的其他课程情况比如在页面上提供相关课程的导航链接等情况将课程列表信息添加到ModelAndView对象中。
List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", course.getSubjectId()).orderAsc(Arrays.asList("sort"))); List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", course.getSubjectId()).orderAsc(Arrays.asList("sort")));
// 将查询到的章节列表信息添加到ModelAndView对象中设置属性名为"chapterList",方便在视图中通过这个属性名获取章节列表数据进行展示,例如循环展示章节名称等相关内容。
model.addObject("chapterList", chapterList); model.addObject("chapterList", chapterList);
// 同样将同学科下的课程列表信息添加到ModelAndView对象中属性名为"courseList",以便在视图中展示相关课程信息,为用户提供更多课程相关的导航或参考信息。
model.addObject("courseList", courseList); model.addObject("courseList", courseList);
// 将查询到的当前课程的详细信息添加到ModelAndView对象中属性名为"course",方便在视图中展示课程的各种详细内容,如课程名称、简介等信息。
model.addObject("course", course); model.addObject("course", course);
// 把查询到的课程所属学科的详细信息添加到ModelAndView对象中属性名为"subject",用于在视图中展示学科相关情况,例如学科名称等内容,让用户了解课程所属的学科背景。
model.addObject("subject", subject); model.addObject("subject", subject);
// 将获取到的与当前课程相关的知识点列表信息添加到ModelAndView对象中属性名为"knowPointList",方便在视图中展示课程包含的知识点情况,例如列出知识点名称等内容。
model.addObject("knowPointList", knowPointList); model.addObject("knowPointList", knowPointList);
model.addObject("knowPoint" , knowPoint); // 把前面获取到的知识点对象如果存在的话添加到ModelAndView对象中属性名为"knowPoint",在视图中可能用于展示某个特定知识点相关的详细信息或者作为关联其他数据的依据等情况(具体取决于视图的业务逻辑设计)。
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) { model.addObject("knowPoint", knowPoint);
model.setViewName("mobile/chapter");
}else { // 通过BrowserUtils工具类的isMobile方法判断请求头中的"user-agent"信息是否表示当前访问是来自移动端设备,
model.setViewName("chapter"); // 如果是移动端访问设置ModelAndView的视图名称为"mobile/chapter",表示返回移动端对应的章节相关视图页面(具体视图页面的内容和样式由对应的前端页面模板决定);
} // 如果不是移动端访问,设置视图名称为"chapter",表示返回普通桌面端等设备对应的章节相关视图页面,根据不同设备类型展示合适的页面给用户查看。
if (BrowserUtils.isMobile(request.getHeader("user-agent"))) {
model.setViewName("mobile/chapter");
} else {
model.setViewName("chapter");
}
return model; return model;
} }
/**
* IDGET"course/findChapter.html"
* iChapterServicefindCourseChapterIDcourseIdList<ChapterEntity>
*
*
* @param courseId iChapterServicefindCourseChapter
* @return List<ChapterEntity>iChapterServicefindCourseChapter
* @ResponseBodyJSON
*/
@RequestMapping(value = {"course/findChapter.html"}, method = RequestMethod.GET) @RequestMapping(value = {"course/findChapter.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<ChapterEntity> findChapterByCourseId(String courseId){ public List<ChapterEntity> findChapterByCourseId(String courseId) {
return iChapterService.findCourseChapter(courseId); return iChapterService.findCourseChapter(courseId);
} }
}
}

@ -11,57 +11,117 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
import com.tamguo.modules.tiku.model.ChapterEntity; import com.tamguo.modules.tiku.model.ChapterEntity;
import com.tamguo.modules.tiku.model.queue.LearnChapterQueue; import com.tamguo.modules.tiku.model.queue.LearnChapterQueue;
import com.tamguo.utils.BrowserUtils; import com.tamguo.utils.BrowserUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理多个不同页面相关的请求,包括首页、第三方验证页面以及错误页面等的展示逻辑。
@Controller @Controller
public class IndexController { public class IndexController {
private Logger logger = LoggerFactory.getLogger(getClass()); // 创建一个日志记录器对象,用于记录在这个控制器类中发生的一些关键操作、调试信息或者错误信息等,
// 通过LoggerFactory根据当前类的Class对象获取对应的Logger实例方便后续在代码中进行日志输出操作便于调试和查看运行情况。
@RequestMapping(value = {"/","index"}, method = RequestMethod.GET) private Logger logger = LoggerFactory.getLogger(getClass());
public ModelAndView indexAction(ModelAndView model , HttpServletRequest request) {
// request url /**
logger.info("request url :{}" , request.getRequestURI()); * "/""index"GET
BlockingQueue<ChapterEntity> chapterQueue = LearnChapterQueue.getChapterQueue(); * BlockingQueue<ChapterEntity>ModelAndView
model.addObject("chapterList", chapterQueue.toArray()); * 访ModelAndViewSpring MVC
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) { *
model.setViewName("mobile/index"); * @param model ModelAndViewSpring MVC
}else { * @param request HttpServletRequestURIrequest.getRequestURI()
model.setViewName("index"); * "user-agent"BrowserUtils访
} * @return ModelAndViewSpring MVC
*/
@RequestMapping(value = {"/", "index"}, method = RequestMethod.GET)
public ModelAndView indexAction(ModelAndView model, HttpServletRequest request) {
// 使用日志记录器记录请求的URL信息方便后续查看请求访问情况以及进行调试等操作通过格式化输出的方式将请求的URI信息记录到日志中
// 输出的日志级别为INFO通常用于记录一些重要的运行时信息方便运维人员或者开发人员了解系统的访问情况。
logger.info("request url :{}", request.getRequestURI());
// 从LearnChapterQueue类中获取章节队列对象BlockingQueue<ChapterEntity>),这个队列可能是用于存储学习相关章节信息的一种数据结构,
// 后续会将队列中的数据添加到ModelAndView对象中传递给视图进行展示具体展示方式取决于前端页面如何处理这些章节数据例如可能展示章节列表等情况
BlockingQueue<ChapterEntity> chapterQueue = LearnChapterQueue.getChapterQueue();
// 将章节队列中的数据转换为数组形式添加到ModelAndView对象中设置属性名为"chapterList",方便在视图中通过这个属性名获取章节相关数据进行展示,
// 例如在首页上循环展示章节名称等相关内容(具体展示逻辑由前端页面模板决定)。
model.addObject("chapterList", chapterQueue.toArray());
// 通过BrowserUtils工具类的isMobile方法判断请求头中的"user-agent"信息是否表示当前访问是来自移动端设备,
// 如果是移动端访问设置ModelAndView的视图名称为"mobile/index",表示返回移动端对应的首页视图页面(具体视图页面的内容和样式由对应的前端页面模板决定);
// 如果不是移动端访问,设置视图名称为"index",表示返回普通桌面端等设备对应的首页视图页面,根据不同设备类型展示合适的首页内容给用户查看。
if (BrowserUtils.isMobile(request.getHeader("user-agent"))) {
model.setViewName("mobile/index");
} else {
model.setViewName("index");
}
return model; return model;
} }
/**
* "baidu_verify_5agfTbCO3Q"GET
* "thirdparty/baidu_verify_5agfTbCO3Q"使Spring MVC
*
* @param model ModelAndView"thirdparty/baidu_verify_5agfTbCO3Q"便Spring MVC
* @return ModelAndViewSpring MVC"baidu_verify_5agfTbCO3Q"
*/
@RequestMapping(value = "/baidu_verify_5agfTbCO3Q", method = RequestMethod.GET) @RequestMapping(value = "/baidu_verify_5agfTbCO3Q", method = RequestMethod.GET)
public ModelAndView baidu_verify_5agfTbCO3Q(ModelAndView model) { public ModelAndView baidu_verify_5agfTbCO3Q(ModelAndView model) {
model.setViewName("thirdparty/baidu_verify_5agfTbCO3Q"); model.setViewName("thirdparty/baidu_verify_5agfTbCO3Q");
return model; return model;
} }
/**
* "baidu_verify_iAm7387J0l"GET
* "thirdparty/baidu_verify_iAm7387J0l"便Spring MVC
*
* @param model ModelAndView"thirdparty/baidu_verify_iAm7387J0l"便Spring MVC
* @return ModelAndViewSpring MVC"baidu_verify_iAm7387J0l"
*/
@RequestMapping(value = "/baidu_verify_iAm7387J0l", method = RequestMethod.GET) @RequestMapping(value = "/baidu_verify_iAm7387J0l", method = RequestMethod.GET)
public ModelAndView baidu_verify_iAm7387J0l(ModelAndView model) { public ModelAndView baidu_verify_iAm7387J0l(ModelAndView model) {
model.setViewName("thirdparty/baidu_verify_iAm7387J0l"); model.setViewName("thirdparty/baidu_verify_iAm7387J0l");
return model; return model;
} }
/**
* "sogousiteverification"GET
* "thirdparty/sogousiteverification"Spring MVC
*
* @param model ModelAndView"thirdparty/sogousiteverification"便Spring MVC
* @return ModelAndViewSpring MVC
*/
@RequestMapping(value = "/sogousiteverification", method = RequestMethod.GET) @RequestMapping(value = "/sogousiteverification", method = RequestMethod.GET)
public ModelAndView sogousiteverification(ModelAndView model) { public ModelAndView sogousiteverification(ModelAndView model) {
model.setViewName("thirdparty/sogousiteverification"); model.setViewName("thirdparty/sogousiteverification");
return model; return model;
} }
@RequestMapping(value = "error404", method = RequestMethod.GET) /**
* 404GET访404
* "404"使Spring MVC
*
* @param model ModelAndView"404"便Spring MVC
* @return ModelAndViewSpring MVC404访
*/
@RequestMapping(value = "error404", method = RequestMethod.GET)
public ModelAndView error404(ModelAndView model) { public ModelAndView error404(ModelAndView model) {
model.setViewName("404"); model.setViewName("404");
return model; return model;
} }
@RequestMapping(value = "error500", method = RequestMethod.GET) /**
* 500GET500
* "500"Spring MVC
*
* @param model ModelAndView"500"便Spring MVC
* @return ModelAndViewSpring MVC500
*/
@RequestMapping(value = "error500", method = RequestMethod.GET)
public ModelAndView error500(ModelAndView model) { public ModelAndView error500(ModelAndView model) {
model.setViewName("500"); model.setViewName("500");
return model; return model;
} }
}
}

@ -24,38 +24,96 @@ import com.tamguo.modules.tiku.service.IChapterService;
import com.tamguo.modules.tiku.service.ICourseService; import com.tamguo.modules.tiku.service.ICourseService;
import com.tamguo.modules.tiku.service.ISubjectService; import com.tamguo.modules.tiku.service.ISubjectService;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与知识点相关的业务操作,具体为展示知识点详情页面以及获取相关联的数据信息等功能对应的请求处理逻辑。
@Controller @Controller
public class KnowPointController { public class KnowPointController {
// 创建一个日志记录器对象,用于记录在这个控制器类中发生的一些关键操作、调试信息或者错误信息等,
// 通过LoggerFactory根据当前类的Class对象获取对应的Logger实例方便后续在代码中进行日志输出操作便于调试和查看运行情况。
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
// 通过@Autowired注解自动注入IKnowPointService接口的实现类实例IKnowPointService用于处理与知识点相关的各种业务逻辑
// 比如根据知识点ID查询知识点详细信息、获取某个课程下的知识点列表等操作在这个控制器的index方法中会调用其相关方法来实现具体的知识点相关业务功能。
@Autowired @Autowired
IKnowPointService iKnowPointService; IKnowPointService iKnowPointService;
// 自动注入IChapterService接口的实现类实例IChapterService用于处理与章节相关的各种业务逻辑
// 例如查询章节树结构等操作,在知识点详情页面展示时,可能需要获取与知识点相关联的章节信息进行展示,会调用其相应方法来获取相关章节数据。
@Autowired @Autowired
IChapterService iChapterService; IChapterService iChapterService;
// 注入ISubjectService接口的实现类实例ISubjectService用于处理与学科科目相关的业务逻辑
// 像根据学科ID查询学科详细信息等操作在知识点详情页面展示时会获取知识点所属学科的相关信息进行展示需要调用该服务层接口的方法来获取学科数据。
@Autowired @Autowired
ISubjectService iSubjectService; ISubjectService iSubjectService;
// 注入ICourseService接口的实现类实例ICourseService主要负责处理与课程相关的业务逻辑
// 比如根据课程ID查询课程信息、获取某个学科下的课程列表等操作在知识点详情页面展示相关数据时也会涉及到课程相关信息的获取与展示会调用它的相应方法来完成。
@Autowired @Autowired
ICourseService iCourseService; ICourseService iCourseService;
/**
* GET"knowpoint/{uid}.html"{uid}
*
* ModelAndView"knowpoint"ModelAndViewSpring MVC
*
* @param uid @PathVariable
* @param model ModelAndView
* "knowpoint"Spring MVC
* @param request HttpServletRequestURIrequest.getRequestURI()便访
* @return ModelAndViewSpring MVC
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"knowpoint/{uid}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"knowpoint/{uid}.html"}, method = RequestMethod.GET)
public ModelAndView index(@PathVariable String uid , ModelAndView model , HttpServletRequest request) { public ModelAndView index(@PathVariable String uid, ModelAndView model, HttpServletRequest request) {
// request url // 使用日志记录器记录请求的URL信息方便后续查看请求访问情况以及进行调试等操作通过格式化输出的方式将请求的URI信息记录到日志中
logger.info("request url :{} " , request.getRequestURI()); // 输出的日志级别为INFO通常用于记录一些重要的运行时信息方便运维人员或者开发人员了解系统的访问情况。
logger.info("request url :{} ", request.getRequestURI());
// 调用iKnowPointService的selectById方法传入知识点的唯一标识uid作为参数从数据库等数据源中查询对应的知识点详细信息以KnowPointEntity对象形式返回
// 获取到的知识点信息后续会添加到ModelAndView对象中传递给视图进行展示例如展示知识点名称、描述等相关内容。
KnowPointEntity knowpoint = iKnowPointService.selectById(uid); KnowPointEntity knowpoint = iKnowPointService.selectById(uid);
// 调用iSubjectService的selectById方法传入知识点对象中的学科IDknowpoint.getSubjectId()作为参数从数据库等数据源中查询对应的学科详细信息以SubjectEntity对象形式返回
// 获取学科信息是为了在知识点详情页面展示该知识点所属的学科相关情况例如学科名称等内容将学科信息添加到ModelAndView对象中传递给视图进行展示。
SubjectEntity subject = iSubjectService.selectById(knowpoint.getSubjectId()); SubjectEntity subject = iSubjectService.selectById(knowpoint.getSubjectId());
// 调用iCourseService的selectList方法通过MyBatis Plus的条件构造器Condition创建查询条件
// 使用eq("subject_id", subject.getId())表示筛选出学科ID等于当前知识点所属学科ID的课程列表再通过orderAsc(Arrays.asList("sort"))按照"sort"字段升序排序课程列表,
// 获取与该知识点所属学科相关的课程列表信息以List<CourseEntity>形式返回用于在知识点详情页面展示同属一个学科下的其他课程情况比如在页面上提供相关课程的导航链接等情况将课程列表信息添加到ModelAndView对象中。
List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", subject.getId()).orderAsc(Arrays.asList("sort"))); List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", subject.getId()).orderAsc(Arrays.asList("sort")));
// 调用iKnowPointService的selectList方法通过条件构造器构建查询条件
// 使用eq("course_id", knowpoint.getCourseId())表示筛选出课程ID等于当前知识点所属课程ID的知识点列表获取与该知识点同属一个课程的其他知识点信息以List<KnowPointEntity>形式返回),
// 这些知识点信息会添加到ModelAndView对象中方便在知识点详情页面展示同课程下的其他知识点情况例如在页面上列出其他知识点名称等相关内容。
List<KnowPointEntity> knowPointList = iKnowPointService.selectList(Condition.create().eq("course_id", knowpoint.getCourseId())); List<KnowPointEntity> knowPointList = iKnowPointService.selectList(Condition.create().eq("course_id", knowpoint.getCourseId()));
// 调用iCourseService的selectById方法传入知识点对象中的课程IDknowpoint.getCourseId()作为参数从数据库等数据源中查询对应的课程详细信息以CourseEntity对象形式返回
// 获取课程信息是为了在知识点详情页面展示该知识点所属的课程相关情况例如课程名称等内容将课程信息添加到ModelAndView对象中传递给视图进行展示。
CourseEntity course = iCourseService.selectById(knowpoint.getCourseId()); CourseEntity course = iCourseService.selectById(knowpoint.getCourseId());
// 调用iChapterService的findChapterTree方法传入知识点的IDknowpoint.getId()作为参数查询与该知识点相关的章节树结构信息以List<ChapterEntity>形式返回),
// 章节树结构信息可能是一种层级嵌套的数据结构方便在页面上以树状形式展示章节的层级关系等情况获取到的章节列表信息会添加到ModelAndView对象中用于视图展示。
List<ChapterEntity> chapterList = iChapterService.findChapterTree(knowpoint.getId()); List<ChapterEntity> chapterList = iChapterService.findChapterTree(knowpoint.getId());
// 将查询到的知识点详细信息添加到ModelAndView对象中设置属性名为"knowpoint",方便在视图中通过这个属性名获取知识点的各种详细内容进行展示,如知识点名称、描述等信息。
model.addObject("knowpoint", knowpoint); model.addObject("knowpoint", knowpoint);
// 把查询到的知识点所属学科的详细信息添加到ModelAndView对象中属性名为"subject",用于在视图中展示学科相关情况,例如学科名称等内容,让用户了解知识点所属的学科背景。
model.addObject("subject", subject); model.addObject("subject", subject);
// 将获取到的知识点所属课程的详细信息添加到ModelAndView对象中属性名为"course",方便在视图中展示课程相关情况,例如课程名称等信息。
model.addObject("course", course); model.addObject("course", course);
model.addObject("chapterList" , chapterList); // 把查询到的与知识点相关的章节列表信息添加到ModelAndView对象中属性名为"chapterList",方便在视图中展示章节相关信息,例如以树状结构展示章节层级关系等情况。
model.addObject("chapterList", chapterList);
// 将同属一个学科下的课程列表信息添加到ModelAndView对象中属性名为"courseList",以便在视图中展示相关课程信息,为用户提供更多课程相关的导航或参考信息。
model.addObject("courseList", courseList); model.addObject("courseList", courseList);
// 把获取到的同课程下的其他知识点列表信息添加到ModelAndView对象中属性名为"knowPointList",方便在视图中展示其他知识点情况,例如列出知识点名称等内容。
model.addObject("knowPointList", knowPointList); model.addObject("knowPointList", knowPointList);
// 设置ModelAndView的视图名称为"knowpoint",表示返回名为"knowpoint"的视图页面供Spring MVC框架根据这个视图名称进行视图渲染并展示知识点详情页面给客户端展示相应的知识点及其关联数据给用户查看。
model.setViewName("knowpoint"); model.setViewName("knowpoint");
return model; return model;
} }
}
}

@ -30,113 +30,156 @@ import java.util.List;
/** /**
* Controller - * Controller -
* * Spring MVCHTTP
*
*
*
* @author candy.tam * @author candy.tam
* *
*/ */
@Controller @Controller
public class PaperController { public class PaperController {
// 创建一个日志记录器对象,用于记录在这个控制器类中发生的一些关键操作、调试信息或者错误信息等,
// 通过LoggerFactory根据当前类的Class对象获取对应的Logger实例方便后续在代码中进行日志输出操作便于调试和查看运行情况。
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
// 通过@Autowired注解自动注入ICourseService接口的实现类实例ICourseService用于处理与课程相关的各种业务逻辑
// 比如根据课程ID查询课程信息、获取某个学科下的课程列表等操作在试卷相关操作中如试卷列表页面展示时关联课程信息等情况会调用其相关方法来实现具体的课程相关业务功能。
@Autowired @Autowired
private ICourseService iCourseService; private ICourseService iCourseService;
// 注入ISysAreaService接口的实现类实例ISysAreaService用于处理与系统区域相关的业务逻辑
// 例如根据区域ID查询区域信息、获取特定层级的区域列表等操作在试卷相关业务中可能涉及按区域筛选试卷等情况会用到该服务层接口的方法来获取区域相关数据。
@Autowired @Autowired
private ISysAreaService iSysAreaService; private ISysAreaService iSysAreaService;
// 自动注入IPaperService接口的实现类实例IPaperService主要负责处理与试卷相关的业务逻辑
// 像根据试卷ID查询试卷信息、获取符合特定条件的试卷列表分页查询并结合各种筛选条件、查找试卷包含的题目信息等操作都会调用它的相应方法来完成。
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
// 注入IQuestionService接口的实现类实例IQuestionService用于处理与题目相关的业务逻辑
// 在试卷详情页面展示时,需要获取试卷包含的题目列表进行展示,会调用其相关方法来获取题目相关数据。
@Autowired @Autowired
private IQuestionService iQuestionService; private IQuestionService iQuestionService;
// 注入ISubjectService接口的实现类实例ISubjectService用于处理与学科科目相关的业务逻辑
// 例如根据学科ID查询学科详细信息等操作在试卷相关操作中如试卷列表页面展示所属学科信息、试卷详情页面关联学科信息等情况会调用该服务层接口的方法来获取学科数据。
@Autowired @Autowired
private ISubjectService iSubjectService; private ISubjectService iSubjectService;
/**
* GET"paperlist/{subjectId}-{courseId}-{paperType}-{year}-{area}-{pageNum}.html"
* { }IDID
*
* 访ModelAndViewSpring MVC
*
* @param request HttpServletRequestURIrequest.getRequestURI()
* "user-agent"BrowserUtils访
* @param subjectId @PathVariable
* @param courseId
* @param paperType 使
* @param year 便2023
* @param area
* @param pageNum Page便
* @param model ModelAndView
* Spring MVC
* @return ModelAndViewSpring MVC
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"paperlist/{subjectId}-{courseId}-{paperType}-{year}-{area}-{pageNum}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"paperlist/{subjectId}-{courseId}-{paperType}-{year}-{area}-{pageNum}.html"}, method = RequestMethod.GET)
public ModelAndView indexAction(HttpServletRequest request , @PathVariable String subjectId , @PathVariable String courseId , @PathVariable String paperType, public ModelAndView indexAction(HttpServletRequest request, @PathVariable String subjectId, @PathVariable String courseId, @PathVariable String paperType,
@PathVariable String year , @PathVariable String area , @PathVariable Integer pageNum, ModelAndView model) { @PathVariable String year, @PathVariable String area, @PathVariable Integer pageNum, ModelAndView model) {
// request url // 使用日志记录器记录请求的URL信息方便后续查看请求访问情况以及进行调试等操作通过格式化输出的方式将请求的URI信息记录到日志中
logger.info("request url :{}" , request.getRequestURI()); // 输出的日志级别为INFO通常用于记录一些重要的运行时信息方便运维人员或者开发人员了解系统的访问情况。
CourseEntity course = iCourseService.selectById(courseId); logger.info("request url :{}", request.getRequestURI());
SysAreaEntity currArea = iSysAreaService.selectById(area);
// 调用iCourseService的selectById方法传入课程的唯一标识courseId作为参数从数据库等数据源中查询对应的课程详细信息以CourseEntity对象形式返回
// 获取到的课程信息后续会添加到ModelAndView对象中传递给视图进行展示例如展示课程名称、课程简介等相关内容同时也可能用于其他关联查询或筛选操作如筛选该课程下的试卷等情况
CourseEntity course = iCourseService.selectById(courseId);
// 调用iSysAreaService的selectById方法传入区域的标识area作为参数从数据库等数据源中查询对应的区域详细信息以SysAreaEntity对象形式返回
// 获取到的区域信息在这里命名为currArea表示当前筛选的区域会添加到ModelAndView对象中方便在试卷列表页面展示区域相关情况例如区域名称等内容也可能用于后续的筛选或关联操作。
SysAreaEntity currArea = iSysAreaService.selectById(area);
// 调用iCourseService的selectList方法通过MyBatis Plus的条件构造器Condition创建查询条件
// 使用eq("subject_id", subjectId)表示筛选出学科ID等于当前传入学科IDsubjectId的课程列表再通过orderBy("sort", true)按照"sort"字段升序排序课程列表,
// 获取与该学科相关的课程列表信息以List<CourseEntity>形式返回用于在试卷列表页面展示同属一个学科下的其他课程情况比如在页面上提供相关课程的导航链接等情况将课程列表信息添加到ModelAndView对象中。
List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", subjectId).orderBy("sort", true)); List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", subjectId).orderBy("sort", true));
SubjectEntity subject = iSubjectService.selectById(subjectId);
List<SysAreaEntity> areaList = iSysAreaService.selectList(Condition.create().eq("tree_level", "0")); // 调用iSubjectService的selectById方法传入学科的唯一标识subjectId作为参数从数据库等数据源中查询对应的学科详细信息以SubjectEntity对象形式返回
// 获取学科信息是为了在试卷列表页面展示该试卷列表所属的学科相关情况例如学科名称等内容将学科信息添加到ModelAndView对象中传递给视图进行展示。
Page<PaperEntity> page = new Page<>(pageNum , 10); SubjectEntity subject = iSubjectService.selectById(subjectId);
Condition condition = Condition.create();
if(!StringUtils.isEmpty(subjectId) && !"0".equals(subjectId)) { // 调用iSysAreaService的selectList方法通过条件构造器构建查询条件
condition.eq("subject_id", subjectId); // 使用eq("tree_level", "0")表示筛选出层级为0的区域列表可能表示顶级区域或者根区域等情况具体含义由业务中区域层级的定义决定获取区域列表信息以List<SysAreaEntity>形式返回),
} // 这些区域列表信息会添加到ModelAndView对象中方便在试卷列表页面展示可筛选的区域情况例如提供区域下拉框供用户选择等情况。
if(!StringUtils.isEmpty(paperType) && !"0".equals(paperType)) { List<SysAreaEntity> areaList = iSysAreaService.selectList(Condition.create().eq("tree_level", "0"));
condition.eq("type", paperType);
} // 创建一个Page对象用于进行分页查询操作传入当前页码pageNum和每页显示的记录数量这里固定为10来确定分页情况
if(!StringUtils.isEmpty(courseId) && !"0".equals(courseId)) { // 后续将这个Page对象以及其他构建的查询条件一起传递给iPaperService的selectPage方法进行分页查询符合条件的试卷列表信息。
condition.eq("course_id", courseId); Page<PaperEntity> page = new Page<>(pageNum, 10);
}
if(!StringUtils.isEmpty(year) && !"0".equals(year)) { // 创建一个Condition对象用于构建MyBatis Plus的查询条件后续根据传入的各个参数情况往这个条件对象中添加相应的条件用于筛选符合要求的试卷列表信息。
condition.eq("year", year); Condition condition = Condition.create();
}
if(!StringUtils.isEmpty(area) && !"0".equals(area)) { // 判断学科ID是否不为空且不等于"0"(可能"0"表示不进行学科筛选等特殊情况,具体由业务逻辑决定),
condition.eq("area_id", area); // 如果满足条件使用eq("subject_id", subjectId)向条件对象中添加学科ID的筛选条件即筛选出学科ID等于传入学科ID的试卷列表信息。
} if (!StringUtils.isEmpty(subjectId) &&!"0".equals(subjectId)) {
PageUtils result = PageUtils.getPage(iPaperService.selectPage(page , condition)); condition.eq("subject_id", subjectId);
if(courseList.size() > 0 && course == null) { }
course = courseList.get(0);
} // 类似地,判断试卷类型是否不为空且不等于"0",如果是,则添加试卷类型的筛选条件,筛选出符合该类型要求的试卷列表信息,例如只查询真题、模拟题等不同类型的试卷。
Integer total = iPaperService.selectCount(Condition.EMPTY); if (!StringUtils.isEmpty(paperType) &&!"0".equals(paperType)) {
model.addObject("courseList", courseList); condition.eq("type", paperType);
model.addObject("subject", subject); }
model.addObject("course", course);
model.addObject("areaList", areaList); // 判断课程ID是否满足相应条件若满足则添加课程ID的筛选条件用于获取该课程相关的试卷信息例如筛选出属于某个具体课程的试卷列表。
model.addObject("paperPage" , result); if (!StringUtils.isEmpty(courseId) &&!"0".equals(courseId)) {
model.addObject("total" , total); condition.eq("course_id", courseId);
model.addObject("courseId", courseId); }
model.addObject("paperType", paperType);
model.addObject("year", year); // 对年份参数进行类似的判断和条件添加操作筛选出对应年份的试卷列表信息比如只展示2023年的试卷等情况。
model.addObject("area", area); if (!StringUtils.isEmpty(year) &&!"0".equals(year)) {
model.addObject("currArea", currArea); condition.eq("year", year);
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) {
model.setViewName("mobile/paperlist");
}else {
model.setViewName("paperlist");
}
return model;
}
@SuppressWarnings("unchecked")
@RequestMapping(value = {"/paper/{paperId}.html"}, method = RequestMethod.GET)
public ModelAndView indexAction(HttpServletRequest request , @PathVariable String paperId , ModelAndView model){
try {
// request url
logger.info("request url :{}" , request.getRequestURI());
model.setViewName("paper");
PaperEntity paper = iPaperService.selectById(paperId);
model.addObject("paper", paper);
model.addObject("subject", StringUtils.isEmpty(paper.getSubjectId()) ? null : iSubjectService.selectById(paper.getSubjectId()));
model.addObject("course", StringUtils.isEmpty(paper.getCourseId()) ? null : iCourseService.selectById(paper.getCourseId()));
// 查询试卷题目
model.addObject("questionList", iPaperService.findQuestionList(paperId));
// 获取推荐试卷
model.addObject("zhentiPaperList", iPaperService.selectPage(new Page<PaperEntity>(1, 5) , Condition.create().eq("subject_id", paper.getSubjectId()).eq("type",SystemConstant.ZHENGTI_PAPER_ID)).getRecords());
model.addObject("moniPaperList", iPaperService.selectPage(new Page<PaperEntity>(1, 5) , Condition.create().eq("subject_id", paper.getSubjectId()).eq("type",SystemConstant.MONI_PAPER_ID)).getRecords());
model.addObject("yatiPaperList", iPaperService.selectPage(new Page<PaperEntity>(1, 5) , Condition.create().eq("subject_id", paper.getSubjectId()).eq("type",SystemConstant.YATI_PAPER_ID)).getRecords());
model.addObject("hotPaperList", iPaperService.selectPage(new Page<PaperEntity>(1, 5) , Condition.create().eq("subject_id", paper.getSubjectId()).eq("course_id", paper.getCourseId())).getRecords());
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) {
model.setViewName("mobile/paper");
}else {
model.setViewName("paper");
}
return model;
} catch (Exception e) {
model.setViewName("404");
return model;
} }
} // 根据区域ID参数情况添加区域筛选条件获取该区域相关的试卷信息确保查询到的试卷列表是与指定区域相关的。
if (!StringUtils.isEmpty(area) &&!"0".equals(area)) {
} condition.eq("area_id", area);
}
// 调用PageUtils工具类的getPage方法传入iPaperService的selectPage方法返回的结果即分页查询到的试卷列表信息进行一些分页相关的处理具体处理逻辑由PageUtils类决定
// 获取处理后的分页数据结果以PageUtils对象形式返回包含了分页相关的数据如总记录数、当前页码、每页记录数等信息以及试卷记录列表等内容将其添加到ModelAndView对象中用于在视图中展示试卷列表以及分页相关的情况。
PageUtils result = PageUtils.getPage(iPaperService.selectPage(page, condition));
// 判断课程列表的大小是否大于0且当前课程对象为null可能存在传入的课程ID对应的课程不存在但有同学科下的其他课程情况
// 如果满足条件说明可以从课程列表中取第一个课程作为默认展示的课程具体业务逻辑可能根据实际情况而定将其赋值给course变量后续添加到ModelAndView对象中用于视图展示。
if (courseList.size() > 0 && course == null) {
course = courseList.get(0);
}
// 调用iPaperService的selectCount方法传入空的Condition对象表示不添加额外筛选条件可能用于获取全部试卷的总数等情况具体取决于业务逻辑
// 查询试卷的总数量以整数形式返回将试卷总数添加到ModelAndView对象中方便在页面上展示试卷的总数量等相关信息例如提示用户共有多少份试卷等情况。
Integer total = iPaperService.selectCount(Condition.EMPTY);
// 将查询到的课程列表信息添加到ModelAndView对象中设置属性名为"courseList",方便在视图中通过这个属性名获取课程列表数据进行展示,例如循环展示课程名称等相关内容。
model.addObject("courseList", courseList);
// 把学科信息添加到ModelAndView对象中属性名为"subject",用于在视图中展示学科相关情况,例如学科名称等内容,让用户了解试卷所属的学科背景。
model.addObject("subject", subject);
// 将当前课程的详细信息添加到ModelAndView对象中属性名为"course",方便在视图中展示课程的各种详细内容,如课程名称、简介等信息。
model.addObject("course", course);
// 把区域列表信息添加到ModelAndView对象中属性名为"areaList",以便在视图中展示可筛选的区域情况,例如以下拉框形式展示区域选项等情况。
model.addObject("areaList", areaList);
// 将处理后的试卷分页数据添加到ModelAndView对象中属性名为"paperPage",方便在视图中展示试卷列表以及分页相关的信息,例如展示试卷标题、分页导航条等内容。
model.addObject("paperPage", result);
// 把试卷总数添加到ModelAndView对象中属性名为"total",用于在视图中展示试卷的总数量相关信息,给用户一个整体的试卷数量提示。
model.addObject("total", total);
// 将课程ID添加到ModelAndView对象中属性名为"courseId",可能在页面上用于一些与课程相关的操作或者链接生成等情况(具体取决于前端页面的业务逻辑设计)。
model.addObject("courseId", courseId);
// 同样把试卷类型添加到ModelAndView对象中属性名为"paperType",方便在页面上展示当前筛选的试卷类型情况,或者用于根据类型进行其他相关操作等情况。
model.addObject("paperType", paperType);
// 将年份信息添加到ModelAndView对象中属性名为"year",用于在视图中展示当前筛选的年份情况,例如提示用户当前展示的是哪一年的试卷等情况。
model.addObject("year", year);
// 把区域标识添加到ModelAndView对象中属性名为"area",可能在页面上用于显示当前筛选的区域情况或者进行区域相关的操作等情况。

@ -26,95 +26,157 @@ import com.tamguo.modules.tiku.model.enums.QuestionTypeEnum;
import com.tamguo.modules.tiku.model.queue.LearnChapterQueue; import com.tamguo.modules.tiku.model.queue.LearnChapterQueue;
import com.tamguo.utils.BrowserUtils; import com.tamguo.utils.BrowserUtils;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与题目相关的各种业务操作,例如展示题目列表页面、单个题目详情页面以及提供获取题目信息的接口等功能。
@Controller @Controller
public class QuestionContrller { public class QuestionContrller {
// 创建一个日志记录器对象,用于记录在这个控制器类中发生的一些关键操作、调试信息或者错误信息等,
// 通过LoggerFactory根据当前类的Class对象获取对应的Logger实例方便后续在代码中进行日志输出操作便于调试和查看运行情况。
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
// 通过@Autowired注解自动注入IQuestionService接口的实现类实例IQuestionService用于处理与题目相关的各种业务逻辑
// 比如根据题目ID查询题目详细信息、进行题目列表的分页查询等操作在这个控制器的各个方法中会调用其相关方法来实现具体的题目相关业务功能。
@Autowired @Autowired
private IQuestionService iQuestionService; private IQuestionService iQuestionService;
// 自动注入IChapterService接口的实现类实例IChapterService用于处理与章节相关的各种业务逻辑
// 例如根据章节ID查询章节信息、获取章节相关的下一章信息等操作在题目相关操作中如题目列表按章节筛选、题目关联所属章节等情况会调用其相关方法来获取章节相关数据。
@Autowired @Autowired
private IChapterService iChapterService; private IChapterService iChapterService;
// 注入ISubjectService接口的实现类实例ISubjectService用于处理与学科科目相关的业务逻辑
// 像根据学科ID查询学科详细信息等操作在题目相关操作中如题目列表展示所属学科信息、题目关联所属学科等情况会调用该服务层接口的方法来获取学科数据。
@Autowired @Autowired
private ISubjectService iSubjectService; private ISubjectService iSubjectService;
// 注入ICourseService接口的实现类实例ICourseService主要负责处理与课程相关的业务逻辑
// 比如根据课程ID查询课程信息、题目关联所属课程等操作在题目相关业务中会调用它的相应方法来获取课程相关数据以及进行关联查询等操作。
@Autowired @Autowired
private ICourseService iCourseService; private ICourseService iCourseService;
// 注入IQuestionAnswerService接口的实现类实例IQuestionAnswerService用于处理与题目答案相关的业务逻辑
// 在展示题目详情页面时,需要获取题目对应的答案列表进行展示,会调用其相关方法来获取答案相关数据。
@Autowired @Autowired
private IQuestionAnswerService iQuestionAnswerService; private IQuestionAnswerService iQuestionAnswerService;
// 注入IQuestionOptionsService接口的实现类实例IQuestionOptionsService用于处理与题目选项相关的业务逻辑
// 例如获取题目包含的选项列表等操作,在展示题目详情以及题目列表时,需要为题目设置对应的选项信息,会调用其相关方法来获取选项数据并进行相应设置。
@Autowired @Autowired
private IQuestionOptionsService iQuestionOptionsService; private IQuestionOptionsService iQuestionOptionsService;
// 章节 // 创建一个阻塞队列BlockingQueue用于存储章节实体对象ChapterEntity队列容量设置为10
// 可能用于在某些业务逻辑中对章节信息进行缓存、排队处理或者传递章节相关数据从当前代码来看在questionList方法中有向这个队列添加章节信息的操作具体作用取决于整体业务需求。
BlockingQueue<ChapterEntity> chapterQueue = new LinkedBlockingQueue<ChapterEntity>(10); BlockingQueue<ChapterEntity> chapterQueue = new LinkedBlockingQueue<ChapterEntity>(10);
/**
* GET"questionlist/{chapterId}-{current}-{size}.html"
* {chapterId}{current}{size}
*
* 访ModelAndViewSpring MVC
*
* @param chapterId @PathVariable
* @param current Page便
* @param size Page便
* @param model ModelAndView
* Spring MVC
* @param request HttpServletRequestURIrequest.getRequestURI()
* "user-agent"BrowserUtils访
* @return ModelAndViewSpring MVC
* @throws InterruptedException 使BlockingQueueLearnChapterQueue.add(chapter)
* 线线
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"questionlist/{chapterId}-{current}-{size}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"questionlist/{chapterId}-{current}-{size}.html"}, method = RequestMethod.GET)
public ModelAndView questionList(@PathVariable String chapterId , @PathVariable Integer current , public ModelAndView questionList(@PathVariable String chapterId, @PathVariable Integer current,
@PathVariable Integer size , ModelAndView model , HttpServletRequest request) throws InterruptedException{ @PathVariable Integer size, ModelAndView model, HttpServletRequest request) throws InterruptedException {
// request url // 使用日志记录器记录请求的URL信息方便后续查看请求访问情况以及进行调试等操作通过格式化输出的方式将请求的URI信息记录到日志中
logger.info("request url :{} " , request.getRequestURI()); // 输出的日志级别为INFO通常用于记录一些重要的运行时信息方便运维人员或者开发人员了解系统的访问情况。
logger.info("request url :{} ", request.getRequestURI());
// 调用iChapterService的selectById方法传入章节的唯一标识chapterId作为参数从数据库等数据源中查询对应的章节详细信息以ChapterEntity对象形式返回
// 获取到的章节信息后续会用于关联查询其他相关数据如所属课程等情况也会添加到ModelAndView对象中传递给视图进行展示例如展示章节名称等相关内容。
ChapterEntity chapter = iChapterService.selectById(chapterId); ChapterEntity chapter = iChapterService.selectById(chapterId);
// 调用iCourseService的selectById方法传入章节对象中的课程IDchapter.getCourseId()作为参数从数据库等数据源中查询对应的课程详细信息以CourseEntity对象形式返回
// 获取课程信息是为了在题目列表页面展示题目所属的课程相关情况例如课程名称等内容将课程信息添加到ModelAndView对象中传递给视图进行展示同时也用于后续关联查询所属学科信息等操作。
CourseEntity course = iCourseService.selectById(chapter.getCourseId()); CourseEntity course = iCourseService.selectById(chapter.getCourseId());
// 调用iSubjectService的selectById方法传入课程对象中的学科IDcourse.getSubjectId()作为参数从数据库等数据源中查询对应的学科详细信息以SubjectEntity对象形式返回
// 获取学科信息用于在题目列表页面展示该题目列表所属的学科相关情况例如学科名称等内容将学科信息添加到ModelAndView对象中传递给视图进行展示。
SubjectEntity subject = iSubjectService.selectById(course.getSubjectId()); SubjectEntity subject = iSubjectService.selectById(course.getSubjectId());
// 调用iChapterService的selectById方法传入章节对象中的父章节代码chapter.getParentCode()作为参数从数据库等数据源中查询对应的父章节详细信息以ChapterEntity对象形式返回
// 获取父章节信息可能用于在题目列表页面展示章节的层级关系等情况例如显示当前章节的上级章节名称等内容将父章节信息添加到ModelAndView对象中传递给视图进行展示。
ChapterEntity parentChapter = iChapterService.selectById(chapter.getParentCode()); ChapterEntity parentChapter = iChapterService.selectById(chapter.getParentCode());
ChapterEntity nextChapter = iChapterService.selectNextChapter(chapter.getParentCode() , chapter.getId());
// 调用iChapterService的selectNextChapter方法传入章节的父章节代码chapter.getParentCode()和当前章节的IDchapter.getId()作为参数查询当前章节的下一章信息以ChapterEntity对象形式返回
// 获取下一章信息可以用于在题目列表页面提供章节导航功能等情况例如提示用户下一章的相关信息或者提供跳转到下一章的链接等将下一章信息添加到ModelAndView对象中传递给视图进行展示。
ChapterEntity nextChapter = iChapterService.selectNextChapter(chapter.getParentCode(), chapter.getId());
// 创建一个Page对象用于进行分页查询操作先实例化一个空的Page对象后续会设置当前页码和每页记录数等属性来确定具体的分页情况。
Page<QuestionEntity> page = new Page<>(); Page<QuestionEntity> page = new Page<>();
// 设置Page对象的当前页码属性传入从请求路径中获取的当前页码current参数告诉服务层要获取第几页的题目数据以便进行分页查询操作。
page.setCurrent(current); page.setCurrent(current);
// 设置Page对象的每页记录数属性传入从请求路径中获取的每页题目数量size参数确定分页查询时每页显示的题目数量使得查询返回符合要求的题目分页列表信息。
page.setSize(size); page.setSize(size);
Page<QuestionEntity> questionList = iQuestionService.selectPage(page , Condition.create().eq("chapter_id", chapterId).orderDesc(Arrays.asList("id")));
for(int i=0 ;i<questionList.getRecords().size() ; i++) { // 调用iQuestionService的selectPage方法传入创建好的Page对象和通过MyBatis Plus的条件构造器Condition创建的查询条件
// 使用eq("chapter_id", chapterId)表示筛选出章节ID等于当前传入章节IDchapterId的题目列表再通过orderDesc(Arrays.asList("id"))按照题目ID降序排序题目列表
// 进行分页查询操作获取符合章节筛选条件且按要求排序的题目分页数据以Page<QuestionEntity>对象形式返回),其中包含了题目记录列表以及分页相关的信息(如总记录数、当前页码、每页记录数等)。
Page<QuestionEntity> questionList = iQuestionService.selectPage(page, Condition.create().eq("chapter_id", chapterId).orderDesc(Arrays.asList("id")));
// 循环遍历题目分页列表中的每一条题目记录,对每个题目进行相关数据设置操作,主要是设置题目选项和题目类型描述信息。
for (int i = 0; i < questionList.getRecords().size(); i++) {
// 从题目分页列表中获取当前遍历到的题目实体对象,用于后续设置其选项和类型描述等相关信息。
QuestionEntity question = questionList.getRecords().get(i); QuestionEntity question = questionList.getRecords().get(i);
List<QuestionOptionsEntity> questionOptions = iQuestionOptionsService.selectList(Condition.create().eq("question_id" , question.getId())); // 调用iQuestionOptionsService的selectList方法通过条件构造器构建查询条件
// 使用eq("question_id", question.getId())表示筛选出题目ID等于当前题目ID的选项列表获取该题目包含的所有选项信息以List<QuestionOptionsEntity>形式返回),
// 然后将获取到的选项列表设置到题目对象的questionOptions属性中方便在视图中展示题目对应的选项内容例如选择题的各个选项等情况。
List<QuestionOptionsEntity> questionOptions = iQuestionOptionsService.selectList(Condition.create().eq("question_id", question.getId()));
question.setQuestionOptions(questionOptions); question.setQuestionOptions(questionOptions);
// 通过QuestionTypeEnum枚举类的getQuestionType方法传入题目对象中的题目类型字段question.getQuestionType())获取对应的题目类型枚举对象,
// 再调用该枚举对象的getDesc方法获取题目类型的描述信息例如将题目类型代码转换为具体的文字描述如"单选题"、"多选题"等情况将题目类型描述设置到题目对象的questionType属性中
// 以便在视图中展示题目类型的详细描述信息,让用户更清晰地了解题目的类型情况。
question.setQuestionType(QuestionTypeEnum.getQuestionType(question.getQuestionType()).getDesc()); question.setQuestionType(QuestionTypeEnum.getQuestionType(question.getQuestionType()).getDesc());
} }
// 将查询到的学科信息添加到ModelAndView对象中设置属性名为"subject",方便在视图中通过这个属性名获取学科列表数据进行展示,例如展示学科名称等相关内容,让用户了解题目所属的学科背景。
model.addObject("subject", subject); model.addObject("subject", subject);
// 把课程信息添加到ModelAndView对象中属性名为"course",用于在视图中展示课程相关情况,例如课程名称等内容,让用户知道题目所属的课程信息。
model.addObject("course", course); model.addObject("course", course);
// 将章节信息添加到ModelAndView对象中属性名为"chapter",方便在视图中展示章节相关情况,例如章节名称、章节顺序等内容,让用户明确题目所属的章节情况。
model.addObject("chapter", chapter); model.addObject("chapter", chapter);
model.addObject("parentChapter" , parentChapter); // 把父章节信息添加到ModelAndView对象中属性名为"parentChapter",用于在视图中展示章节的层级关系情况,例如显示当前章节的上级章节名称等内容。
model.addObject("nextChapter" , nextChapter); model.addObject("parentChapter", parentChapter);
// 将下一章信息添加到ModelAndView对象中属性名为"nextChapter",方便在视图中展示章节导航相关情况,例如提示用户下一章的相关信息或者提供跳转到下一章的链接等。
model.addObject("nextChapter", nextChapter);
// 把处理好的题目分页列表信息添加到ModelAndView对象中属性名为"questionList",以便在视图中展示题目列表以及分页相关的信息,例如展示题目标题、分页导航条等内容。
model.addObject("questionList", questionList); model.addObject("questionList", questionList);
// 将课程的学科ID添加到ModelAndView对象中属性名为"subjectId",可能在页面上用于一些与学科相关的操作或者链接生成等情况(具体取决于前端页面的业务逻辑设计)。
model.addObject("subjectId", course.getSubjectId()); model.addObject("subjectId", course.getSubjectId());
// 同样把课程的唯一标识添加到ModelAndView对象中属性名为"courseId"方便在页面上进行一些与课程相关的操作例如根据课程ID查询其他课程相关信息等情况。
model.addObject("courseId", course.getId()); model.addObject("courseId", course.getId());
// 将当前章节信息添加到LearnChapterQueue队列中假设LearnChapterQueue是一个用于管理章节信息的自定义队列类
// 具体作用可能是用于缓存章节信息、后续进行章节相关的统计或其他业务逻辑处理等情况这里将章节添加到队列中方便在其他地方使用该章节数据具体取决于LearnChapterQueue类的具体实现和业务用途
LearnChapterQueue.add(chapter); LearnChapterQueue.add(chapter);
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) { // 通过BrowserUtils工具类的isMobile方法判断请求头中的"user-agent"信息是否表示当前访问是来自移动端设备,
model.setViewName("mobile/questionList"); // 如果是移动端访问设置ModelAndView的视图名称为"mobile/questionList",表示返回移动端对应的题目列表视图页面(具体视图页面的内容和样式由对应的前端页面模板决定);
}else { // 如果不是移动端访问,设置视图名称为"questionList",表示返回普通桌面端等设备对应的题目列表视图页面,根据不同设备类型展示合适的题目列表内容给用户查看。
model.setViewName("questionList"); if (BrowserUtils.isMobile(request.getHeader("user-agent"))) {
} model.setViewName("mobile/questionList");
return model; } else {
} model.setViewName("questionList");
}
/**
* 访
* @param uid
* @param model
* @return
*/
@SuppressWarnings("unchecked")
@RequestMapping(value = {"/question/{uid}.html"}, method = RequestMethod.GET)
public ModelAndView question(@PathVariable String uid , ModelAndView model , HttpServletRequest request){
// request url
logger.info("request url :{}" , request.getRequestURI());
model.setViewName("question");
QuestionEntity question = iQuestionService.selectById(uid);
List<QuestionOptionsEntity> questionOptions = iQuestionOptionsService.selectList(Condition.create().eq("question_id" , question.getId()));
question.setQuestionOptions(questionOptions);
question.setQuestionType(QuestionTypeEnum.getQuestionType(question.getQuestionType()).getDesc());
model.addObject("question", question);
model.addObject("course", iCourseService.selectById(question.getCourseId()));
model.addObject("answerList", iQuestionAnswerService.selectList(Condition.create().eq("question_id", uid).orderDesc(Arrays.asList("create_date"))));
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) {
model.setViewName("mobile/question");
}else {
model.setViewName("question");
}
return model; return model;
} }
/**
* 访GET
@RequestMapping(value = {"question/getQuestion/{uid}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"question/getQuestion/{uid}.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody

@ -10,21 +10,38 @@ import com.aliyuncs.exceptions.ClientException;
import com.tamguo.common.utils.Result; import com.tamguo.common.utils.Result;
import com.tamguo.modules.sys.service.ISmsService; import com.tamguo.modules.sys.service.ISmsService;
// @Controller注解用于将这个类标记为Spring MVC框架中的一个控制器组件意味着Spring容器会在启动时扫描到这个类并将其管理起来
// 使得这个类中的方法能够处理对应的HTTP请求根据请求的映射关系通过@RequestMapping等注解定义调用相应的业务逻辑方法然后返回视图或者数据响应给客户端
// 在这里主要用于处理与短信发送(具体为发送找回密码相关短信)的业务操作,即接收前端请求并调用相关服务层方法来尝试发送短信,然后将结果返回给前端。
@Controller @Controller
public class SmsController { public class SmsController {
// 通过@Autowired注解自动注入ISmsService接口的实现类实例ISmsService用于处理与短信发送相关的各种业务逻辑
// 比如在这里具体用于发送找回密码的短信在sendFindPasswordSms方法中会调用其对应的发送短信方法来实现具体的短信发送功能。
@Autowired @Autowired
ISmsService iSmsService; ISmsService iSmsService;
/**
* GET"sms/sendFindPasswordSms.html"mobile
* iSmsServicesendFindPasswordSmsResult
* ClientExceptionResult500
*
* @param mobile iSmsServicesendFindPasswordSms
* @return ResultiSmsServicesendFindPasswordSmsResult@ResponseBodyResultJSON
* Result500
*/
@RequestMapping(value = {"sms/sendFindPasswordSms.html"}, method = RequestMethod.GET) @RequestMapping(value = {"sms/sendFindPasswordSms.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result sendFindPasswordSms(String mobile){ public Result sendFindPasswordSms(String mobile) {
try { try {
// 调用iSmsService的sendFindPasswordSms方法传入要接收短信的手机号码mobile作为参数尝试发送找回密码相关的短信该方法会根据业务逻辑与短信服务提供商如阿里云短信服务等具体取决于ISmsService的实现进行交互
// 进行短信内容组装、发送等操作并返回一个Result对象其中包含了短信发送的结果相关信息如状态码表示发送是否成功、提示信息等内容后续会将这个Result对象返回给前端告知前端短信发送的情况。
return iSmsService.sendFindPasswordSms(mobile); return iSmsService.sendFindPasswordSms(mobile);
} catch (ClientException e) { } catch (ClientException e) {
// 若在调用iSmsService的sendFindPasswordSms方法发送短信过程中出现ClientException异常可能是短信服务配置问题、网络问题等导致与阿里云短信服务通信异常等情况
// 则通过e.printStackTrace()打印异常的堆栈信息方便开发人员进行调试和定位问题所在然后返回一个表示失败的Result对象状态码为500无具体数据提示信息为空字符串给前端告知前端短信发送出现了问题发送失败。
e.printStackTrace(); e.printStackTrace();
} }
return Result.result(500, null, ""); return Result.result(500, null, "");
} }
}
}

@ -24,74 +24,168 @@ import java.util.List;
/** /**
* Controller - * Controller -
* * Spring MVCHTTP
*
*
* @author candy.tam * @author candy.tam
* *
*/ */
@Controller @Controller
public class SubjectController { public class SubjectController {
// 创建一个日志记录器对象,用于记录在这个控制器类中发生的一些关键操作、调试信息或者错误信息等,
// 通过LoggerFactory根据当前类的Class对象获取对应的Logger实例方便后续在代码中进行日志输出操作便于调试和查看运行情况。
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
// 通过@Autowired注解自动注入IChapterService接口的实现类实例IChapterService用于处理与章节相关的各种业务逻辑
// 比如根据章节相关条件查询章节列表、通过书籍ID获取章节列表等操作在考试科目相关页面展示中可能需要获取章节信息进行展示会调用其相关方法来获取章节数据。
@Autowired @Autowired
private IChapterService iChapterService; private IChapterService iChapterService;
// 注入ISysAreaService接口的实现类实例ISysAreaService用于处理与系统区域相关的业务逻辑
// 例如获取特定层级如层级为0的顶级区域等情况的区域列表等操作在考试科目相关页面展示时可能会展示区域相关信息比如用于筛选试卷等情况虽然当前代码中未明确体现完整相关逻辑会用到该服务层接口的方法来获取区域数据。
@Autowired @Autowired
private ISysAreaService iSysAreaService; private ISysAreaService iSysAreaService;
// 自动注入ISubjectService接口的实现类实例ISubjectService主要负责处理与学科科目相关的业务逻辑
// 像根据学科ID查询学科详细信息、获取课程级联树数据、获取学科树数据等操作都会调用它的相应方法来完成是处理考试科目相关业务的核心服务层接口之一。
@Autowired @Autowired
private ISubjectService iSubjectService; private ISubjectService iSubjectService;
// 注入ICourseService接口的实现类实例ICourseService用于处理与课程相关的业务逻辑
// 例如根据学科ID查询该学科下的课程列表、获取课程相关信息等操作在考试科目相关页面展示以及其他业务逻辑中如获取课程级联树数据等情况会调用其相应方法来获取课程相关数据。
@Autowired @Autowired
private ICourseService iCourseService; private ICourseService iCourseService;
// 注入IKnowPointService接口的实现类实例IKnowPointService用于处理知识点相关的业务逻辑在这里代码中变量命名为iBookService可能知识点与书籍有一定关联或者从业务角度将知识点当作书籍看待等情况具体取决于业务含义
// 比如根据课程ID查询对应的知识点列表等操作在构建考试科目相关页面数据时会涉及到知识点书籍相关信息的获取与处理会调用其相应方法。
@Autowired @Autowired
private IKnowPointService iBookService; private IKnowPointService iBookService;
// 注入IPaperService接口的实现类实例IPaperService用于处理与试卷相关的业务逻辑
// 例如根据学科ID分页查询最新的试卷列表等操作在考试科目相关页面展示时会获取该学科相关的试卷信息进行展示需要调用它的相应方法来获取试卷数据。
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
/**
* GET"subject/{subjectId}.html"{subjectId}
*
* 访ModelAndViewSpring MVC
*
* @param subjectId @PathVariable
* @param request HttpServletRequestURIrequest.getRequestURI()
* "user-agent"BrowserUtils访
* @param model ModelAndView
* Spring MVC
* @return ModelAndViewSpring MVC
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"subject/{subjectId}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"subject/{subjectId}.html"}, method = RequestMethod.GET)
public ModelAndView indexAction(@PathVariable String subjectId , HttpServletRequest request , ModelAndView model) { public ModelAndView indexAction(@PathVariable String subjectId, HttpServletRequest request, ModelAndView model) {
// request url // 使用日志记录器记录请求的URL信息方便后续查看请求访问情况以及进行调试等操作通过格式化输出的方式将请求的URI信息记录到日志中
logger.info("request url :{} " , request.getRequestURI() ); // 输出的日志级别为INFO通常用于记录一些重要的运行时信息方便运维人员或者开发人员了解系统的访问情况。
logger.info("request url :{} ", request.getRequestURI());
// 调用iSubjectService的selectById方法传入考试科目的唯一标识subjectId作为参数从数据库等数据源中查询对应的考试科目详细信息以SubjectEntity对象形式返回
// 获取到的考试科目信息后续会添加到ModelAndView对象中传递给视图进行展示例如展示科目名称、科目简介等相关内容。
SubjectEntity subject = iSubjectService.selectById(subjectId); SubjectEntity subject = iSubjectService.selectById(subjectId);
// 调用iCourseService的selectList方法通过MyBatis Plus的条件构造器Condition创建查询条件
// 使用eq("subject_id", subjectId)表示筛选出学科ID等于当前传入考试科目IDsubjectId的课程列表再通过orderAsc(Arrays.asList("sort"))按照"sort"字段升序排序课程列表,
// 获取与该考试科目相关的课程列表信息以List<CourseEntity>形式返回用于在考试科目详情页面展示同属一个科目的课程情况比如在页面上列出课程名称等相关内容将课程列表信息添加到ModelAndView对象中。
List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", subjectId).orderAsc(Arrays.asList("sort"))); List<CourseEntity> courseList = iCourseService.selectList(Condition.create().eq("subject_id", subjectId).orderAsc(Arrays.asList("sort")));
// 获取第一个科目
// 从获取到的课程列表中获取第一个课程对象(这里假设可以基于第一个课程来获取相关知识点、章节等信息,具体业务逻辑可能根据实际情况而定),
// 获取这个课程对象主要是为了后续基于它来获取相关的知识点、章节等数据,进行页面数据的进一步组装和展示操作。
CourseEntity course = courseList.get(0); CourseEntity course = courseList.get(0);
// 获取第一本书
// 调用iBookService实际为IKnowPointService的selectList方法通过条件构造器构建查询条件
// 使用eq("course_id", course.getId())表示筛选出课程ID等于当前获取到的课程IDcourse.getId()的知识点列表这里看作书籍列表具体取决于业务含义获取与该课程相关的所有知识点书籍信息以List<KnowPointEntity>形式返回),
// 这些知识点书籍信息后续可能用于获取与之关联的章节信息等操作也会添加到ModelAndView对象中虽然当前代码中未明确在视图中如何展示可能根据业务需求有相应的处理逻辑
List<KnowPointEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", course.getId())); List<KnowPointEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", course.getId()));
// 初始化章节列表为null后续会根据知识点书籍情况来确定章节列表的值如果有对应的知识点书籍存在会进一步查询相关章节信息进行赋值。
List<ChapterEntity> chapterList = null; List<ChapterEntity> chapterList = null;
if(bookList.size() > 0) {
// 判断知识点书籍列表的大小是否大于0即是否存在与当前课程相关的知识点书籍
// 如果存在知识点(书籍),说明可以基于它们进一步获取与之关联的章节信息等内容,进行后续的数据准备操作。
if (bookList.size() > 0) {
// 从知识点(书籍)列表中获取第一个知识点(书籍)对象(同样假设基于第一个对象来获取章节信息,具体业务逻辑依实际情况而定),
// 获取这个知识点书籍对象主要是为了获取其ID作为查询章节列表的依据获取与该知识点书籍相关联的章节信息。
KnowPointEntity book = bookList.get(0); KnowPointEntity book = bookList.get(0);
// 调用iChapterService的selectList方法传入通过条件构造器构建的查询条件
// 使用eq("book_id", book.getId())表示筛选出书籍ID等于当前获取到的知识点书籍IDbook.getId()的章节列表获取与该知识点书籍相关的章节列表信息以List<ChapterEntity>形式返回),
// 章节列表信息可能用于在考试科目详情页面展示课程对应的章节情况例如章节名称等内容具体展示逻辑由前端页面模板决定获取到的章节列表信息会赋值给chapterList变量后续添加到ModelAndView对象中用于视图展示。
chapterList = iChapterService.selectList(Condition.create().eq("book_id", book.getId())); chapterList = iChapterService.selectList(Condition.create().eq("book_id", book.getId()));
} }
// 获取最新的试卷
Page<PaperEntity> paperPage = iPaperService.selectPage(new Page<PaperEntity>(1, 15) , Condition.create().eq("subject_id", subjectId).orderDesc(Arrays.asList("id"))); // 调用iPaperService的selectPage方法创建一个Page对象用于分页查询传入当前页码为1和每页显示记录数为15表示获取第一页且每页显示15条记录
model.addObject("subject", subject); // 通过条件构造器构建查询条件使用eq("subject_id", subjectId)表示筛选出学科ID等于当前考试科目IDsubjectId的试卷列表再通过orderDesc(Arrays.asList("id"))按照试卷ID降序排序试卷列表
model.addObject("course" , course); // 进行分页查询操作获取符合学科筛选条件且按要求排序的试卷分页数据以Page<PaperEntity>对象形式返回这里只取第一页的试卷记录即最新的试卷列表假设ID越大越新等情况具体取决于业务中试卷ID的定义和排序规则
model.addObject("courseList", courseList); // 获取到的试卷列表信息会添加到ModelAndView对象中用于在考试科目详情页面展示该科目相关的试卷情况例如展示试卷标题等相关内容。
model.addObject("chapterList" , chapterList); Page<PaperEntity> paperPage = iPaperService.selectPage(new Page<PaperEntity>(1, 15), Condition.create().eq("subject_id", subjectId).orderDesc(Arrays.asList("id")));
model.addObject("areaList", iSysAreaService.selectList(Condition.create().eq("tree_level", "0")));
model.addObject("paperList", paperPage.getRecords()); // 将查询到的考试科目详细信息添加到ModelAndView对象中设置属性名为"subject",方便在视图中通过这个属性名获取科目详细内容进行展示,如科目名称、简介等信息。
if(BrowserUtils.isMobile(request.getHeader("user-agent"))) { model.addObject("subject", subject);
model.setViewName("mobile/subject"); // 把获取到的当前课程信息添加到ModelAndView对象中属性名为"course",用于在视图中展示课程相关情况,例如课程名称等内容,让用户了解考试科目下的具体课程情况。
}else { model.addObject("course", course);
model.setViewName("subject"); // 将同属一个科目的课程列表信息添加到ModelAndView对象中属性名为"courseList",以便在视图中展示课程列表相关信息,例如循环展示课程名称等情况,为用户提供更多课程相关的参考信息。
} model.addObject("courseList", courseList);
return model; // 把获取到的章节列表信息添加到ModelAndView对象中属性名为"chapterList",方便在视图中展示章节相关信息,例如展示章节名称等内容,体现课程对应的章节情况。
} model.addObject("chapterList", chapterList);
// 将区域列表信息添加到ModelAndView对象中属性名为"areaList"通过调用iSysAreaService的selectList方法获取层级为0的区域列表可能表示顶级区域等情况具体含义由业务中区域层级的定义决定
// [{"value":"11","label":"北京市","children":[{"value":"1101","label":"市辖区"}]}] // 方便在视图中展示可筛选的区域情况(虽然当前代码未明确体现完整筛选逻辑,可能后续有相关扩展功能),例如以下拉框形式展示区域选项等情况。
model.addObject("areaList", iSysAreaService.selectList(Condition.create().eq("tree_level", "0")));
// 把获取到的试卷列表信息具体为第一页的试卷记录添加到ModelAndView对象中属性名为"paperList",以便在视图中展示试卷相关信息,例如展示试卷标题等内容,让用户了解该科目相关的试卷情况。
model.addObject("paperList", paperPage.getRecords());
// 通过BrowserUtils工具类的isMobile方法判断请求头中的"user-agent"信息是否表示当前访问是来自移动端设备,
// 如果是移动端访问设置ModelAndView的视图名称为"mobile/subject",表示返回移动端对应的考试科目详情视图页面(具体视图页面的内容和样式由对应的前端页面模板决定);
// 如果不是移动端访问,设置视图名称为"subject",表示返回普通桌面端等设备对应的考试科目详情视图页面,根据不同设备类型展示合适的考试科目详情内容给用户查看。
if (BrowserUtils.isMobile(request.getHeader("user-agent"))) {
model.setViewName("mobile/subject");
} else {
model.setViewName("subject");
}
return model;
}
/**
* GET"subject/getCourseCascaderTree.html"
* iSubjectServicegetCourseCascaderTreeJSONArray
* ResultResult.successResultResult
*
* @return ResultResult.successResultiSubjectServicegetCourseCascaderTreeJSONArray
* @ResponseBodyResultJSON便
*/
@RequestMapping(value = {"subject/getCourseCascaderTree.html"}, method = RequestMethod.GET) @RequestMapping(value = {"subject/getCourseCascaderTree.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result getCourseCascaderTree() { public Result getCourseCascaderTree() {
JSONArray list = iSubjectService.getCourseCascaderTree(); JSONArray list = iSubjectService.getCourseCascaderTree();
return Result.successResult(list); return Result.successResult(list);
} }
// [{"value":"11","label":"北京市"}] /**
* GET"subject/getSubjectTree.html"
* iSubjectServicegetSubjectTreeJSONArray
* ResultResult.successResult
*
* @return ResultResult.successResultiSubjectServicegetSubjectTreeJSONArray
* @ResponseBodyResultJSON便
*/
// 处理获取学科树数据的GET请求请求路径为"subject/getSubjectTree.html"意味着当客户端发起一个GET请求到该路径时此方法会被触发执行。
// 通过这个方法可以获取学科相关的树形结构数据,一般用于在前端页面以树形结构展示学科的层级关系等情况,方便用户直观地查看和操作学科分类信息。
@RequestMapping(value = {"subject/getSubjectTree.html"}, method = RequestMethod.GET) @RequestMapping(value = {"subject/getSubjectTree.html"}, method = RequestMethod.GET)
// @ResponseBody注解用于将方法的返回值直接作为响应体返回给客户端而不是去寻找对应的视图进行渲染。
// 在这里会将获取到的学科树数据以JSONArray形式包装在Result对象中然后以合适的格式如JSON格式取决于项目配置返回给前端供前端进行后续处理和展示。
@ResponseBody @ResponseBody
public Result getSubjectTree() { public Result getSubjectTree() {
// 调用iSubjectService应该是注入的处理学科相关业务逻辑的服务层接口实现类的getSubjectTree方法
// 该方法的作用是从数据源如数据库等中获取学科的树形结构数据返回的数据格式为JSONArray它可以方便地表示具有层级关系的数据结构适合用于前端展示树形结构的场景。
JSONArray list = iSubjectService.getSubjectTree(); JSONArray list = iSubjectService.getSubjectTree();
// 将获取到的JSONArray格式的学科树数据list通过Result.successResult方法进行包装Result对象通常用于统一封装返回给前端的数据格式
// 它里面包含了表示请求是否成功的状态码这里调用successResult方法表示成功状态以及实际的数据内容即学科树数据list然后将这个包装好的Result对象返回给前端
// 前端可以根据Result对象中的状态码判断请求是否成功并提取其中的学科树数据进行相应的展示和处理比如在页面上以树形菜单等形式展示学科的层级结构等情况。
return Result.successResult(list); return Result.successResult(list);
} }
}

Loading…
Cancel
Save