会员中心dcx (#1)

Co-authored-by: pu36s7yfv <2320898596@qq.com>
Co-committed-by: pu36s7yfv <2320898596@qq.com>
develop
pu36s7yfv 8 months ago committed by pbn32vpxk
parent 2152260c1f
commit d8eae693f8

@ -10,26 +10,58 @@ 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;
// 这是Spring Boot应用的主启动类使用了 @SpringBootApplication 注解,该注解是一个组合注解,相当于同时使用了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 注解,
// 意味着它标记这个类是一个配置类会自动配置Spring Boot应用的很多默认设置并且会自动扫描和加载应用中的各个组件如各种Bean、Controller、Service等
@SpringBootApplication @SpringBootApplication
// 通过 @ComponentScan 注解指定要扫描的基础包路径,这里设置为 "com.tamguo"表示Spring会在该包及其子包下查找带有Spring相关注解如 @Component、@Service、@Controller 等的类并将它们注册为Spring容器中的组件方便后续进行依赖注入等操作。
@ComponentScan("com.tamguo") @ComponentScan("com.tamguo")
public class Application { public class Application {
/**
* Spring Boot SpringApplicationBuilder Application
* run argsSpring BootSpringWebWeb
*
* @param args
*/
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 * JSONHttpMessageConvertersBean
* Spring Boot使JacksonJSON使FastJsonJacksonJSON
* JSON使JSON便
*
* @return HttpMessageConvertersSpringHTTPJSONJavaJSONJSONJava
*/ */
@Bean @Bean
public HttpMessageConverters fastJsonHttpMessageConverters() { public HttpMessageConverters fastJsonHttpMessageConverters() {
// 创建一个FastJsonHttpMessageConverter实例它是基于FastJson实现的HTTP消息转换器用于将Java对象转换为FastJson格式的JSON字符串或者将接收到的FastJson格式的JSON字符串转换为Java对象
// 在后续配置中会设置它的相关属性使其按照项目需求进行JSON数据的转换操作。
FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter(); FastJsonHttpMessageConverter fastConverter = new FastJsonHttpMessageConverter();
// 创建一个FastJsonConfig实例用于配置FastJson的相关参数比如设置日期格式、序列化特性等通过该配置对象可以定制化FastJson序列化和反序列化的行为使其符合项目的具体要求。
FastJsonConfig fastJsonConfig = new FastJsonConfig(); FastJsonConfig fastJsonConfig = new FastJsonConfig();
// 设置FastJson序列化日期类型数据时的格式这里设置为 "yyyy-MM-dd HH:mm:ss"使得在将包含日期属性的Java对象转换为JSON字符串时日期字段会按照该格式进行序列化方便前端进行统一的日期格式展示和处理
// 例如数据库中的日期时间类型数据在转换为JSON发送给前端时会以指定的这种常见的格式化字符串形式呈现便于前端直接展示或者进行日期相关的操作如格式化、比较等
fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss"); fastJsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");
// 设置FastJson的序列化特性这里通过SerializerFeature.DisableCircularReferenceDetect来禁用循环引用检测
// 当Java对象之间存在复杂的关联关系可能形成循环引用时比如对象A包含对象B对象B又包含对象A这样的情况默认情况下FastJson会进行循环引用检测并做相应处理
// 但有时候可能希望关闭这种检测根据具体业务场景和数据结构确定通过设置该特性可以改变FastJson在序列化时对循环引用的处理方式避免可能出现的序列化异常或者生成不符合预期的JSON结构等问题。
fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect); fastJsonConfig.setSerializerFeatures(SerializerFeature.DisableCircularReferenceDetect);
// 将配置好的FastJsonConfig对象设置到FastJsonHttpMessageConverter中使得消息转换器在进行JSON数据转换时应用这些配置参数按照设定的日期格式和序列化特性来处理Java对象与JSON字符串之间的转换操作。
fastConverter.setFastJsonConfig(fastJsonConfig); fastConverter.setFastJsonConfig(fastJsonConfig);
// 将配置好的FastJsonHttpMessageConverter赋值给converter变量这里其实可以直接使用fastConverter赋值这一步略显多余但不影响功能实现可能是代码风格或者后续潜在扩展的考虑
// 后续会使用该变量来创建并返回HttpMessageConverters对象。
FastJsonHttpMessageConverter converter = fastConverter; FastJsonHttpMessageConverter converter = fastConverter;
// 创建并返回一个HttpMessageConverters对象将配置好的FastJsonHttpMessageConverter作为参数传入这样Spring容器就会识别并使用这个自定义的消息转换器来处理HTTP请求和响应中的JSON数据转换
// 替代默认的Jackson相关的消息转换器实现使用FastJson进行JSON数据处理的目的。
return new HttpMessageConverters(converter); return new HttpMessageConverters(converter);
} }

@ -19,23 +19,40 @@ 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;
// 标识这是一个Spring的配置类用于配置MyBatis Plus相关的功能和插件通过定义多个Bean方法来创建并配置不同的MyBatis Plus组件
// 例如性能分析拦截器、分页插件、元对象处理器以及SQL注入器等这些组件将被Spring容器管理并应用到MyBatis Plus的相关操作中影响数据库访问的行为和功能。
@Configuration @Configuration
// 配置MyBatis的Mapper扫描路径告诉MyBatis Plus去指定的包及其子包下扫描接口作为Mapper接口这样MyBatis Plus就能自动为这些Mapper接口生成对应的实现类并注入到Spring容器中方便进行数据库操作。
@MapperScan("com.tamguo.modules.*.dao*") @MapperScan("com.tamguo.modules.*.dao*")
public class MybatisPlusConfig { public class MybatisPlusConfig {
/**
* PerformanceInterceptorBeanMyBatis PlusSQL
* SQL便SQL便
*
* @return PerformanceInterceptorSpringMyBatis Plus
*/
@Bean @Bean
public PerformanceInterceptor performanceInterceptor() { public PerformanceInterceptor performanceInterceptor() {
return new PerformanceInterceptor(); return new PerformanceInterceptor();
} }
/** /**
* mybatis-plus<br> * PaginationInterceptorBeanMyBatis Plus
* http://mp.baomidou.com<br> * LIMIT使便使
* SQL
* <p>
* http://mp.baomidou.com
*
* @return PaginationInterceptorSpringSQL
*/ */
@Bean @Bean
public PaginationInterceptor paginationInterceptor() { public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持 // 设置开启PageHelper的支持这里的PageHelper可能是指与分页相关的某种兼容模式或者特定功能支持具体依赖于MyBatis Plus内部实现以及与其他分页相关框架的整合情况
// 通过开启此功能,可以更好地适配一些已有的分页相关的使用习惯或者与其他分页工具协同工作(具体根据实际业务场景确定)。
paginationInterceptor.setLocalPage(true);
/* /*
* SQL <br> * SQL <br>
* 1 cookie SQL <br> * 1 cookie SQL <br>
@ -45,24 +62,28 @@ public class MybatisPlusConfig {
tenantSqlParser.setTenantHandler(new TenantHandler() { tenantSqlParser.setTenantHandler(new TenantHandler() {
@Override @Override
public Expression getTenantId() { public Expression getTenantId() {
// 返回表示租户ID的表达式这里固定返回一个值为1L的LongValue表达式表示当前租户的ID为1实际业务中通常需要从合适的地方如请求头、Cookie等动态获取租户ID。
return new LongValue(1L); return new LongValue(1L);
} }
@Override @Override
public String getTenantIdColumn() { public String getTenantIdColumn() {
// 返回用于标识租户ID的数据库表列名这里指定为"course_id",意味着在多租户场景下,通过该列的值来区分不同租户的数据,即该列存储了租户的相关标识信息。
return "course_id"; return "course_id";
} }
@Override @Override
public boolean doTableFilter(String tableName) { public boolean doTableFilter(String tableName) {
// 这里可以判断是否过滤表 // 这里可以判断是否过滤表即根据表名决定是否应用多租户的过滤逻辑例如某些公共表可能不需要进行租户隔离可以返回true进行过滤不应用租户相关的SQL解析处理
// 当前直接返回true表示默认对所有表应用过滤逻辑实际业务中需要根据具体的表结构和业务需求来准确判断哪些表需要进行租户相关处理哪些表不需要。
return true; return true;
} }
}); });
// 将配置好的TenantSqlParser添加到SQL解析器列表中后续分页拦截器会遍历该列表中的解析器对SQL语句依次进行解析处理应用相应的逻辑如多租户数据隔离等
sqlParserList.add(tenantSqlParser); sqlParserList.add(tenantSqlParser);
paginationInterceptor.setSqlParserList(sqlParserList); paginationInterceptor.setSqlParserList(sqlParserList);
// 以下过滤方式与 @SqlParser(filter = true) 注解等效 // 以下过滤方式与 @SqlParser(filter = true) 注解等效
// paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() { // paginationInterceptor.setSqlParserFilter(new ISqlParserFilter() {
// @Override // @Override
@ -75,19 +96,31 @@ public class MybatisPlusConfig {
// return false; // return false;
// } // }
// }); // });
return paginationInterceptor; return paginationInterceptor;
} }
/**
* MetaObjectHandlerBeanMyBatis Plus
* Mapper
*
* @return MetaObjectHandlerMyMetaObjectHandlerMetaObjectHandler
* Spring
*/
@Bean @Bean
public MetaObjectHandler metaObjectHandler(){ public MetaObjectHandler metaObjectHandler() {
return new MyMetaObjectHandler(); return new MyMetaObjectHandler();
} }
/** /**
* sql * ISqlInjectorSQLBeanMyBatis PlusSQLLogicSqlInjector
* 便
* SQLMyBatis PlusMapper便使SQL
*
* @return LogicSqlInjectorSQLSpring使使MyBatis Plus使SQL
*/ */
@Bean @Bean
public ISqlInjector sqlInjector(){ public ISqlInjector sqlInjector() {
return new LogicSqlInjector(); return new LogicSqlInjector();
} }
} }

@ -1,6 +1,7 @@
package com.tamguo.config.shiro; package com.tamguo.config.shiro;
import java.util.Set; import java.util.Set;
import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.AuthenticationToken;
@ -20,20 +21,33 @@ import com.tamguo.modules.member.service.IMemberService;
/** /**
* *
* * ShiroAuthorizingRealm
* ShiroRealmShiro
* IMemberService
*/ */
public class MemberRealm extends AuthorizingRealm { public class MemberRealm extends AuthorizingRealm {
// 自动注入IMemberService用于调用会员相关的业务逻辑方法比如根据用户名查找会员信息、获取登录失败次数、更新登录失败次数、更新最后登录时间等操作
// 这些操作都是在认证(验证用户登录合法性)和授权(后续可能基于会员角色等信息获取权限)过程中需要依赖的业务逻辑实现。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/** /**
* () * ()
*/ * Shiro访Shiro
* SimpleAuthorizationInfo
* infoShiro
*
* @param principals
*
* @return AuthorizationInfoSimpleAuthorizationInfoShiro访
*/
@Override @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Set<String > permsSet = null; // 目前只是初始化一个空的权限集合,实际业务中应从数据库等数据源查询并填充相应的权限信息,例如根据用户角色、用户直接关联的权限等获取权限列表。
Set<String> permsSet = null;
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 将权限集合设置到授权信息对象中,当前为空集合,表示没有赋予任何权限,实际情况需要根据业务逻辑查询并设置正确的权限信息。
info.setStringPermissions(permsSet); info.setStringPermissions(permsSet);
return info; return info;
@ -41,32 +55,54 @@ public class MemberRealm extends AuthorizingRealm {
/** /**
* () * ()
* ShiroShiro
*
* ShiroAuthenticationInfo
*
* @param token Shiro
* token.getPrincipal()token.getCredentials()
* @return AuthenticationInfoSimpleAuthenticationInfoRealmShiro
* AuthenticationExceptionShiro
* @throws AuthenticationException Shiro
*/ */
@Override @Override
protected AuthenticationInfo doGetAuthenticationInfo( protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationToken token) throws AuthenticationException { // 从登录凭证对象中获取用户名信息通常前端输入的用户名会被封装到token中作为主体信息这里将其从token中取出并转换为字符串类型用于后续查询数据库验证用户是否存在等操作。
String username = (String) token.getPrincipal(); String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials()); // 从登录凭证对象中获取密码信息token.getCredentials()返回的是字符数组类型,将其转换为字符串类型,用于后续与数据库中存储的用户密码进行比对验证操作。
String password = new String((char[]) token.getCredentials());
// 通过会员服务,根据用户名去数据库中查找对应的会员实体信息,用于后续验证该用户是否存在以及密码是否匹配等操作,如果查找不到则表示用户不存在,需要抛出相应异常。
MemberEntity member = iMemberService.findByUsername(username);
if (member == null) {
// 如果根据用户名未找到对应的会员信息抛出UnknownAccountException异常表示用户名不存在或者未找到对应的用户信息Shiro框架会捕获该异常并返回相应的错误提示给客户端。
throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码");
}
MemberEntity member = iMemberService.findByUsername(username); // 通过会员服务获取当前会员账号的登录失败次数用于后续判断账号是否因为多次登录失败而被锁定具体的锁定阈值这里是10次根据业务需求设定可调整。
if(member == null) { Integer loginFailureCount = iMemberService.getLoginFailureCount(member);
throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码"); if (loginFailureCount > 10) {
} // 如果登录失败次数超过设定的阈值10次抛出LockedAccountException异常表示账号被锁定Shiro框架会捕获该异常并返回相应的错误提示给客户端。
Integer loginFailureCount = iMemberService.getLoginFailureCount(member); throw new LockedAccountException("账号被锁定");
if(loginFailureCount > 10) { }
throw new LockedAccountException("账号被锁定");
}
if(!new Sha256Hash(password).toHex().equals(member.getPassword())){ // 将用户输入的密码进行哈希处理使用Sha256Hash算法进行加密这里将用户输入的密码转换为十六进制字符串形式方便与数据库中存储的加密后的密码进行比对
// 然后与数据库中存储的会员密码进行比对,如果不相等则表示密码错误,需要进行相应的错误处理操作(如记录登录失败次数、抛出异常等)。
if (!new Sha256Hash(password).toHex().equals(member.getPassword())) {
// 如果密码不匹配先将登录失败次数加1表示又一次登录失败然后调用会员服务更新数据库中该会员账号的登录失败次数记录。
loginFailureCount++; loginFailureCount++;
iMemberService.updateLoginFailureCount(member , loginFailureCount); iMemberService.updateLoginFailureCount(member, loginFailureCount);
// 抛出IncorrectCredentialsException异常表示密码错误Shiro框架会捕获该异常并返回相应的错误提示给客户端。
throw new IncorrectCredentialsException("用户名或密码有误,请重新输入或找回密码"); throw new IncorrectCredentialsException("用户名或密码有误,请重新输入或找回密码");
} }
// 更新登录时间
// 如果密码验证通过,调用会员服务更新数据库中该会员账号的最后登录时间,记录用户此次登录的时间信息,方便后续进行一些统计分析或者基于登录时间的业务逻辑处理(如判断账号是否长时间未登录等情况)。
iMemberService.updateLastLoginTime(member.getId()); iMemberService.updateLastLoginTime(member.getId());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName()); // 创建一个SimpleAuthenticationInfo对象用于返回认证成功的信息给Shiro框架将找到的会员实体对象member作为主体信息、用户输入的密码password以及当前Realm的名称通过getName()方法获取)设置到该对象中,
return info; // 表示登录认证成功Shiro框架后续会基于这些信息进行会话管理等相关操作并且可以在后续的请求处理中通过Shiro的相关API获取当前登录用户的信息等内容。
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName());
return info;
} }
} }

@ -12,27 +12,59 @@ 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;
// 标识这是一个Spring的配置类用于配置Shiro框架相关的组件和功能通过定义多个Bean方法来创建并配置Shiro中的核心组件
// 如Realm用于认证和授权的领域对象、缓存管理器、安全管理器、过滤器工厂等这些组件协同工作为Web应用提供基于角色和权限的安全访问控制功能。
@Configuration @Configuration
public class ShiroConfiguration { public class ShiroConfiguration {
// 定义一个静态的LinkedHashMap用于存储URL路径与对应的Shiro过滤器链定义其中键表示URL的匹配模式值表示对应的过滤器链定义例如需要进行何种认证、授权等操作
// 后续会将该映射配置到ShiroFilterFactoryBean中用于控制不同URL的访问权限和安全策略。
private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
/**
* MemberRealmRealmShiroRealmBean
* RealmSecurityManagerShiro
*
* @return MemberRealmSpringShiro使
*/
@Bean(name = "shiroRealm") @Bean(name = "shiroRealm")
public MemberRealm getShiroRealm() { public MemberRealm getShiroRealm() {
return new MemberRealm(); return new MemberRealm();
} }
/**
* EhCacheManagerEhCacheBeanShiro
* classpath:ehcache-shiro.xml
*
* @return EhCacheManagerSpringShiro使
*/
@Bean(name = "shiroEhcacheManager") @Bean(name = "shiroEhcacheManager")
public EhCacheManager getEhCacheManager() { public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager(); EhCacheManager em = new EhCacheManager();
// 设置EhCache的配置文件路径该文件通常定义了缓存的名称、缓存的策略如内存缓存、磁盘缓存等、缓存的过期时间、最大缓存数量等相关配置信息
// Shiro会根据该配置文件来初始化和管理缓存这里使用classpath路径表示从类路径下查找该配置文件。
em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); em.setCacheManagerConfigFile("classpath:ehcache-shiro.xml");
return em; return em;
} }
/**
* LifecycleBeanPostProcessorBeanBeanShiroSpring
* ShiroShiro
*
* @return LifecycleBeanPostProcessorSpring使Shiro
*/
@Bean(name = "lifecycleBeanPostProcessor") @Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor(); return new LifecycleBeanPostProcessor();
} }
/**
* DefaultAdvisorAutoProxyCreatorBeanSpring AOPAdvisor
* ShiroSpring使ShiroAOP
* proxyTargetClasstrue使
*
* @return DefaultAdvisorAutoProxyCreatorSpring使Spring AOPShiro
*/
@Bean @Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
@ -40,14 +72,30 @@ public class ShiroConfiguration {
return daap; return daap;
} }
/**
* DefaultWebSecurityManagerWebBeanShiroWeb
* Realm
* MemberRealmEhCacheManager使Web访
*
* @return DefaultWebSecurityManagerSpringShiroShiroFilterFactoryBean
*/
@Bean(name = "securityManager") @Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager() { public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
// 设置自定义的Realm将之前创建的MemberRealm实例注入到安全管理器中使得安全管理器在进行认证和授权操作时能够调用MemberRealm中实现的具体业务逻辑。
dwsm.setRealm(getShiroRealm()); dwsm.setRealm(getShiroRealm());
// 设置缓存管理器将之前创建的EhCacheManager实例注入到安全管理器中使得安全管理器能够利用缓存来管理用户认证信息、授权信息等提高系统性能和响应速度。
dwsm.setCacheManager(getEhCacheManager()); dwsm.setCacheManager(getEhCacheManager());
return dwsm; return dwsm;
} }
/**
* AuthorizationAttributeSourceAdvisorBeanShiroSpring AOP
* DefaultWebSecurityManager使Shiro
// 例如判断当前用户是否具有访问某个被@RequiresPermissions等授权注解标注的方法的权限从而实现细粒度的权限控制功能。
*
* @return AuthorizationAttributeSourceAdvisorSpring使Spring AOPShiro
*/
@Bean @Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() { public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
@ -55,15 +103,30 @@ public class ShiroConfiguration {
return new AuthorizationAttributeSourceAdvisor(); return new AuthorizationAttributeSourceAdvisor();
} }
/**
* ShiroFilterFactoryBeanShiroBeanBeanShiroWebURL
* URLShiroWeb访URLURL
* Web访URL访URL访
*
* @return ShiroFilterFactoryBeanSpringWebWeb访
*/
@Bean(name = "shiroFilter") @Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean() { public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 设置安全管理器将之前创建的DefaultWebSecurityManager实例注入到ShiroFilterFactoryBean中使得过滤器工厂能够利用安全管理器进行认证、授权等安全相关的操作
// 根据安全管理器中配置的Realm、缓存管理器等组件来判断请求是否合法、用户是否具有访问权限等情况。
shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager()); shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager());
// 设置登录页面的URL当用户访问需要认证但未登录的资源时Shiro会自动重定向到该URL让用户进行登录操作这里设置为"/login",表示应用中对应的登录页面路径。
shiroFilterFactoryBean.setLoginUrl("/login"); shiroFilterFactoryBean.setLoginUrl("/login");
// 设置用户成功登录后的跳转URL当用户登录成功后Shiro会自动重定向到该URL这里设置为"/index",表示登录成功后默认跳转到应用中的首页路径(具体根据业务需求确定)。
shiroFilterFactoryBean.setSuccessUrl("/index"); shiroFilterFactoryBean.setSuccessUrl("/index");
// 将定义好的URL路径与对应的Shiro过滤器链定义的映射添加到ShiroFilterFactoryBean中例如将"/member/**"路径设置为需要进行认证("authc"表示需要进行身份认证的过滤器),
// 将"/**"路径设置为可以匿名访问("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;
} }
} }

@ -11,29 +11,76 @@ import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import com.tamguo.common.utils.SystemConstant; import com.tamguo.common.utils.SystemConstant;
// 标识这是一个Spring的组件类用于配置Thymeleaf视图相关的一些静态变量通过实现EnvironmentAware接口可以获取Spring的应用程序环境信息
// 进而从环境配置中获取特定的属性值并将这些属性值以及一些常量值设置到ThymeleafViewResolver的静态变量中使得在Thymeleaf模板中可以方便地使用这些变量进行页面渲染等操作。
@Component @Component
public class ThymeleafConfig implements EnvironmentAware{ public class ThymeleafConfig implements EnvironmentAware {
// 通过@Resource注解注入Spring的Environment对象用于获取应用程序的配置属性信息例如从配置文件如application.properties或application.yml等中读取各种自定义的配置项值。
@Resource @Resource
private Environment env; private Environment env;
/**
* ThymeleafThymeleafViewResolver
* EnvironmentSystemConstant
* Thymeleaf使便
* ThymeleafViewResolverSpring使
*
* @param viewResolver ThymeleafViewResolver使Thymeleaf访使
*/
@Resource @Resource
private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) { private void configureThymeleafStaticVars(ThymeleafViewResolver viewResolver) {
if(viewResolver != null) { if (viewResolver!= null) {
Map<String, Object> vars = new HashMap<>(); // 创建一个HashMap用于存储要设置的静态变量键为变量名值为对应的变量值后续会将这些变量设置到ThymeleafViewResolver中以便在Thymeleaf模板中使用。
vars.put("domainName", env.getProperty("domain.name")); Map<String, Object> vars = new HashMap<>();
vars.put("adminDomain", env.getProperty("admin.domain.name"));
vars.put("memberDomain", env.getProperty("member.domain.name")); // 从应用程序的配置属性中获取名为"domain.name"的属性值,并将其作为"domainName"变量的值存储到vars映射中
vars.put("tamguoDomain", env.getProperty("tamguo.domain.name")); // 该变量可能用于在Thymeleaf模板中生成完整的域名相关的链接、资源引用等情况例如拼接完整的图片地址、外部接口地址等具体根据业务需求确定。
vars.put("PAPER_TYPE_ZHENTI", SystemConstant.ZHENGTI_PAPER_ID); vars.put("domainName", env.getProperty("domain.name"));
vars.put("PAPER_TYPE_MONI", SystemConstant.MONI_PAPER_ID);
vars.put("PAPER_TYPE_YATI", SystemConstant.YATI_PAPER_ID); // 从应用程序的配置属性中获取名为"admin.domain.name"的属性值,并将其作为"adminDomain"变量的值存储到vars映射中
vars.put("PAPER_TYPE_MINGXIAO", SystemConstant.MINGXIAO_PAPER_ID); // 该变量可能用于在Thymeleaf模板中针对管理员相关的功能模块生成特定域名的链接、资源引用等情况例如管理员登录页面的域名、管理员后台接口的域名等具体根据业务中管理员模块的需求确定。
vars.put("BEIJING_AREA_ID", SystemConstant.BEIJING_AREA_ID); vars.put("adminDomain", env.getProperty("admin.domain.name"));
viewResolver.setStaticVariables(vars);
} // 从应用程序的配置属性中获取名为"member.domain.name"的属性值,并将其作为"memberDomain"变量的值存储到vars映射中
// 该变量可能用于在Thymeleaf模板中针对会员相关的功能模块生成特定域名的链接、资源引用等情况例如会员中心页面的域名、会员相关接口的域名等具体根据业务中会员模块的需求确定。
vars.put("memberDomain", env.getProperty("member.domain.name"));
// 从应用程序的配置属性中获取名为"tamguo.domain.name"的属性值,并将其作为"tamguoDomain"变量的值存储到vars映射中
// 该变量可能用于在Thymeleaf模板中针对特定业务域名以tamguo相关的业务场景生成链接、资源引用等情况具体根据业务中该域名对应的功能模块需求确定。
vars.put("tamguoDomain", env.getProperty("tamguo.domain.name"));
// 从自定义的SystemConstant类中获取名为ZHENGTI_PAPER_ID的常量值并将其作为"PAPER_TYPE_ZHENTI"变量的值存储到vars映射中
// 该变量可能用于在Thymeleaf模板中区分不同类型的试卷相关逻辑处理例如根据试卷类型显示不同的样式、进行不同的操作按钮显示等情况具体根据业务中试卷模块的需求确定。
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模板中涉及北京地区相关的逻辑处理例如根据地区ID进行特定地区的业务展示、数据筛选等情况具体根据业务中地区相关模块的需求确定。
vars.put("BEIJING_AREA_ID", SystemConstant.BEIJING_AREA_ID);
// 将包含所有静态变量的映射设置到ThymeleafViewResolver对象中使得这些变量可以在Thymeleaf模板中被访问和使用实现配置的生效方便页面渲染时的动态数据展示和逻辑处理。
viewResolver.setStaticVariables(vars);
}
} }
/**
* EnvironmentAwareEnvironmentSpring
* 便environmentenv便使
*
* @param environment Spring
*/
@Override @Override
public void setEnvironment(Environment environment) { public void setEnvironment(Environment environment) {
env = environment; env = environment;

@ -12,31 +12,69 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import com.tamguo.interceptor.MemberInterceptor; import com.tamguo.interceptor.MemberInterceptor;
// 标识这是一个Spring的配置类用于配置Spring Web应用相关的功能通过实现WebMvcConfigurer接口并重写其中的方法
// 可以对资源处理器、拦截器以及Cookie序列化等方面进行定制化配置以满足项目特定的业务需求例如设置文件资源的访问路径、添加会员相关请求的拦截逻辑、配置Cookie的相关属性等。
@Configuration @Configuration
public class WebConfig implements WebMvcConfigurer { public class WebConfig implements WebMvcConfigurer {
@Value("${file.storage.path}") // 通过@Value注解从配置文件如application.properties或application.yml等中注入文件存储路径属性值
private String fileStoragePath; // 该路径用于指定服务器上存储文件的实际位置后续在配置文件资源处理器时会用到以便正确地将特定URL路径映射到该文件存储位置实现文件的访问。
@Value("${cookie.domian.name}") @Value("${file.storage.path}")
private String cookieDomianName; private String fileStoragePath;
@Autowired
private MemberInterceptor memberInterceptor;
// 通过@Value注解从配置文件中注入Cookie的域名属性值该域名用于设置Cookie的作用域决定了Cookie在哪些域名下可以被发送和接收
// 在配置Cookie序列化相关逻辑时会使用该值来准确设置Cookie的域名属性确保会话相关的Cookie在正确的域名范围内有效。
@Value("${cookie.domian.name}")
private String cookieDomianName;
// 自动注入MemberInterceptor这是一个自定义的拦截器用于在特定的请求路径下执行一些前置处理逻辑比如可能用于验证会员的登录状态、权限等情况
// 在配置拦截器时会将该拦截器添加到相应的请求路径上,使其生效并发挥作用。
@Autowired
private MemberInterceptor memberInterceptor;
/**
* WebMvcConfigureraddResourceHandlersURL
* 使访URL
*
* @param registry URL
*/
@Override @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) { public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**").addResourceLocations("file:"+fileStoragePath); // 将以"/files/"开头的所有URL路径例如 "/files/image.jpg"、"/files/documents/report.pdf"等),
// 映射到服务器上以"file:"开头的实际文件存储路径通过fileStoragePath变量获取这样客户端访问 "/files/" 相关路径时,就能获取到存储在对应文件存储路径下的文件资源了。
registry.addResourceHandler("/files/**").addResourceLocations("file:" + fileStoragePath);
} }
@Override /**
public void addInterceptors(InterceptorRegistry registry) { * WebMvcConfigureraddInterceptors
registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**"); * 使
} *
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 将自动注入的MemberInterceptor添加到以"/member/"开头的所有请求路径上(例如 "/member/profile"、"/member/orders"等),
// 意味着当客户端发起这些会员相关的请求时MemberInterceptor中的逻辑会先被执行进行相应的前置处理例如检查当前用户是否已登录为会员如果未登录则可能重定向到登录页面等操作。
registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**");
}
/**
* CookieSerializerCookieBeanDefaultCookieSerializer
* Spring SessionCookieCookieCookie
* CookieCookie
*
* @return CookieSerializerSpringSpring SessionCookie使
*/
@Bean @Bean
public CookieSerializer defaultCookieSerializer(){ public CookieSerializer defaultCookieSerializer() {
DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer(); DefaultCookieSerializer defaultCookieSerializer = new DefaultCookieSerializer();
// 设置Cookie的名称为"sessionId"这意味着在客户端和服务器之间传输的会话相关的Cookie名称将是"sessionId"
// 服务器通过该名称来识别和处理对应的会话信息,前端在发送请求时也会带上名为"sessionId"的Cookie来标识当前会话。
defaultCookieSerializer.setCookieName("sessionId"); defaultCookieSerializer.setCookieName("sessionId");
// 设置Cookie的域名使用之前注入的cookieDomianName属性值确定Cookie的作用域使得Cookie只会在指定的域名下有效
// 例如,如果域名设置为"example.com"那么只有访问该域名下的页面时相关的Cookie才会被发送和接收保证会话信息在正确的域名范围内传递。
defaultCookieSerializer.setDomainName(cookieDomianName); defaultCookieSerializer.setDomainName(cookieDomianName);
// 设置Cookie的路径为"/"表示该Cookie在整个域名下的所有路径都有效即无论访问域名下的哪个具体页面路径都会带上这个Cookie方便会话信息在整个应用的各个页面间共享和使用。
defaultCookieSerializer.setCookiePath("/"); defaultCookieSerializer.setCookiePath("/");
return defaultCookieSerializer; return defaultCookieSerializer;
} }

@ -8,48 +8,75 @@ 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;
// 标识这是一个Spring的组件类用于拦截Web请求它继承自HandlerInterceptorAdapter通过重写其中的preHandle方法来实现自定义的请求拦截逻辑
// 主要用于检查用户是否已登录通过查看会话中是否存在特定的用户标识信息根据不同的请求类型如普通的GET请求、AJAX请求等以及登录状态进行相应的处理
// 例如未登录时对于AJAX请求返回特定的错误响应头和状态码对于普通GET请求则重定向到登录页面并携带当前请求的URL作为重定向后的参数对于其他非GET请求则直接重定向到登录页面。
@Component @Component
public class MemberInterceptor extends HandlerInterceptorAdapter{ public class MemberInterceptor extends HandlerInterceptorAdapter {
/** "重定向URL"参数名称 */ /**
* "重定向URL"
* URL便访
*/
private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl"; private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl";
/** 默认登录URL */ /**
* URL
* URLURL使便
*/
private static final String DEFAULT_LOGIN_URL = "/login.html"; private static final String DEFAULT_LOGIN_URL = "/login.html";
/** 登录URL */ /**
* URL
* URLURLDEFAULT_LOGIN_URL使
*/
private String loginUrl = DEFAULT_LOGIN_URL; private String loginUrl = DEFAULT_LOGIN_URL;
// 通过@Value注解从配置文件如application.properties或application.yml等中注入名为"tamguo.domain.name"的属性值,
// 该域名通常用于构建完整的登录页面URL等情况确保重定向到登录页面时使用的是正确的域名使得在不同的部署环境下域名可能不同能准确访问到登录页面。
@Value("${tamguo.domain.name}") @Value("${tamguo.domain.name}")
private String tamguoDomainName; private String tamguoDomainName;
/** /**
* *
* HandlerInterceptorAdapterpreHandle
* AJAXGET
* *
* @param request * @param request HttpServletRequestURL
* HttpServletRequest * @param response HttpServletResponse
* @param response * @param handler Controller使
* HttpServletResponse * @return trueControllerfalse
* @param handler * @throws Exception
*
* @return
*/ */
@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"); // 从当前请求的会话Session中获取名为"currMember"的属性值,该属性通常在用户登录成功后被设置,用于标识当前登录的用户信息,
if (currMember != null) { // 如果能获取到该属性值即不为null表示用户已经登录此时允许请求继续执行后续的处理流程直接返回true。
Object currMember = request.getSession().getAttribute("currMember");
if (currMember!= null) {
return true; return true;
} else { } else {
// 获取请求头中名为"X-Requested-With"的字段值该字段常用于判断请求是否为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")) {
// 如果请求头中的"X-Requested-With"字段值表明这是一个AJAX请求且用户未登录那么向响应头中添加一个名为"loginStatus"的字段,值为"accessDenied"
// 用于告知前端该请求因为未登录被拒绝访问然后通过response发送一个HTTP状态码为FORBIDDEN403表示禁止访问的错误响应给客户端最后返回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等进行不同的重定向处理。
if (request.getMethod().equalsIgnoreCase("GET")) { if (request.getMethod().equalsIgnoreCase("GET")) {
String redirectUrl = request.getQueryString() != null ? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI(); // 如果是GET请求构建要重定向的URL参数部分先判断请求是否带有查询字符串即请求URL中是否包含 "?" 后面的参数部分),
// 如果有查询字符串则将请求的URI不包含域名和端口部分的请求路径加上原有的查询字符串作为重定向的URL参数部分如果没有查询字符串则只使用请求的URI作为重定向的URL参数部分
// 这样做是为了在用户登录成功后能根据这个参数值将用户重定向回原本想要访问的页面。
String redirectUrl = request.getQueryString()!= null? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI();
// 进行重定向操作将用户重定向到登录页面构建完整的登录页面URL通过将配置的域名tamguoDomainName、登录页面的URL路径loginUrl以及重定向URL参数使用URLEncoder进行UTF-8编码确保特殊字符能正确传递拼接起来
// 形成最终的重定向地址发送给客户端让用户先进行登录操作然后返回false拦截该请求中断后续的请求处理流程。
response.sendRedirect(tamguoDomainName + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8")); response.sendRedirect(tamguoDomainName + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8"));
} else { } else {
// 如果是除GET之外的其他请求方法如POST等直接将用户重定向到登录页面构建完整的登录页面URL通过将配置的域名tamguoDomainName和登录页面的URL路径loginUrl拼接起来
// 形成最终的重定向地址发送给客户端让用户先进行登录操作然后返回false拦截该请求中断后续的请求处理流程。
response.sendRedirect(tamguoDomainName + loginUrl); response.sendRedirect(tamguoDomainName + loginUrl);
} }
return false; return false;

@ -5,30 +5,58 @@ import java.io.IOException;
import java.security.MessageDigest; import java.security.MessageDigest;
import org.apache.commons.codec.binary.Hex; import org.apache.commons.codec.binary.Hex;
// 该类是一个工具类主要提供了计算文件MD5值的功能通过读取文件内容并按照一定的算法逐步更新MD5摘要信息最终生成文件的MD5值
// 并且能够处理大文件(通过分块读取文件内容的方式避免一次性将整个大文件读入内存),同时对可能出现的异常进行了相应处理,并确保文件输入流资源在使用后能正确关闭。
public class FileMd5Utils { public class FileMd5Utils {
/** /**
* md5() * md5
* @return md5 value * MD58192
* MD5MD5MD5
* nullMD5
*
* @param fileInputStream MD5MD5
*
* @return MD5nullMD5
*/ */
public static String getMD5(FileInputStream fileInputStream) { public static String getMD5(FileInputStream fileInputStream) {
try { try {
// 获取MD5算法的MessageDigest实例用于计算文件内容的MD5摘要信息MessageDigest类提供了按照特定算法这里是MD5算法计算数据摘要的功能
// 通过调用getInstance方法并传入算法名称"MD5"来创建对应的实例后续可以使用该实例不断更新要计算摘要的数据内容最终生成完整的摘要结果即MD5值对应的字节数组
MessageDigest MD5 = MessageDigest.getInstance("MD5"); MessageDigest MD5 = MessageDigest.getInstance("MD5");
// 创建一个字节数组缓冲区用于每次从文件输入流中读取文件内容的数据块这里设置缓冲区大小为8192字节通过分块读取文件内容的方式
// 可以避免一次性将整个大文件读入内存从而能够处理大文件的MD5计算提高内存使用效率以及避免内存溢出等问题特别是对于大型文件的处理更为合理和可行。
byte[] buffer = new byte[8192]; byte[] buffer = new byte[8192];
// 用于记录每次从文件输入流中实际读取到的字节数,在循环读取文件内容过程中,每次读取操作都会更新该变量的值,通过判断其是否为 -1 来确定是否已经读取完整个文件内容,
// 如果读取到文件末尾则返回 -1表示文件内容已全部读完循环结束。
int length; int length;
while ((length = fileInputStream.read(buffer)) != -1) {
// 通过循环不断从文件输入流中读取文件内容到缓冲区每次读取的数据长度存储在length变量中然后使用MD5摘要对象的update方法将读取到的数据块更新到摘要计算中
// 这样不断更新摘要信息直到读取完整个文件内容即length为 -1 时结束循环实现对整个文件内容的MD5摘要计算。
while ((length = fileInputStream.read(buffer))!= -1) {
MD5.update(buffer, 0, length); MD5.update(buffer, 0, length);
} }
// 通过Hex类来自Apache Commons Codec库的encodeHex方法将最终生成的MD5摘要字节数组转换为十六进制字符串形式
// 这样得到的就是常见的以十六进制字符串表示的文件MD5值方便后续存储、展示以及与其他MD5值进行比对等操作最后将该十六进制字符串作为文件的MD5值返回。
return new String(Hex.encodeHex(MD5.digest())); return new String(Hex.encodeHex(MD5.digest()));
} catch (Exception e) { } catch (Exception e) {
// 如果在获取MD5算法实例、读取文件内容、更新MD5摘要或者转换MD5摘要字节数组为十六进制字符串等过程中出现异常
// 则会打印异常的堆栈信息通过e.printStackTrace()方法方便后续排查问题同时返回null表示获取文件MD5值失败调用者可根据返回值进行相应的错误处理。
e.printStackTrace(); e.printStackTrace();
return null; return null;
} finally { } finally {
try { try {
if (fileInputStream != null){ // 在finally块中确保文件输入流资源能够正确关闭避免因异常等情况导致资源未释放的问题先判断文件输入流是否不为空
// 如果不为空则调用其close方法关闭输入流释放相关的文件资源确保文件读取操作完成后正确关闭资源避免资源泄漏等问题。
if (fileInputStream!= null) {
fileInputStream.close(); fileInputStream.close();
} }
} catch (IOException e) { } catch (IOException e) {
// 如果在关闭文件输入流过程中出现异常(例如文件被其他程序占用无法正常关闭等情况),同样会打印异常的堆栈信息,方便后续查看问题原因,
// 虽然此处无法对关闭流失败进行更多的补救措施,但通过打印异常信息可以帮助了解资源释放出现的问题。
e.printStackTrace(); e.printStackTrace();
} }
} }

@ -6,36 +6,94 @@ import org.apache.shiro.subject.Subject;
import com.tamguo.modules.member.model.MemberEntity; import com.tamguo.modules.member.model.MemberEntity;
// 该类是一个工具类主要提供了一系列基于Shiro框架的便捷方法用于获取Shiro相关的核心对象如Subject、Session等、获取当前登录用户的信息如用户实体、用户ID等、操作会话属性设置和获取以及判断用户是否登录、执行用户登出等功能
// 在整个项目中方便其他地方复用这些与Shiro框架交互的常用操作逻辑避免重复编写相似的代码来获取Shiro相关对象和信息。
public class ShiroUtils { public class ShiroUtils {
/**
* ShiroSession
* ShiroSecurityUtilsgetSubjectSubjectSubject
* 便
*
* @return ShiroSession
*/
public static Session getSession() { public static Session getSession() {
return SecurityUtils.getSubject().getSession(); return SecurityUtils.getSubject().getSession();
} }
/**
* ShiroSubject
* ShiroSubject
* ShiroSecurityUtilsgetSubjectShiro
*
* @return Subject
*/
public static Subject getSubject() { public static Subject getSubject() {
return SecurityUtils.getSubject(); return SecurityUtils.getSubject();
} }
/**
* MemberEntity
* ShiroSecurityUtilsSubjectSubjectPrincipalMemberEntity
* ID便使
*
* @return MemberEntitynull使
*/
public static MemberEntity getMember() { public static MemberEntity getMember() {
return (MemberEntity)SecurityUtils.getSubject().getPrincipal(); return (MemberEntity) SecurityUtils.getSubject().getPrincipal();
} }
/**
* ID
* getMemberMemberEntityID便
* IDID便
*
* @return IDgetMemberMemberEntity使
*/
public static String getMemberId() { public static String getMemberId() {
return getMember().getId(); return getMember().getId();
} }
/**
* ShiroSession
* getSession使setAttributekeyvalue
* 便
*
* @param key ObjectJava
* @param value 便使Object
*/
public static void setSessionAttribute(Object key, Object value) { public static void setSessionAttribute(Object key, Object value) {
getSession().setAttribute(key, value); getSession().setAttribute(key, value);
} }
/**
* ShiroSession
* getSession使getAttributekey
* null便使
*
* @param key Object使
* @return nullObject使
*/
public static Object getSessionAttribute(Object key) { public static Object getSessionAttribute(Object key) {
return getSession().getAttribute(key); return getSession().getAttribute(key);
} }
/**
*
* ShiroSecurityUtilsSubjectPrincipalnullnull
* 便
*
* @return SubjectPrincipalnulltruefalse
*/
public static boolean isLogin() { public static boolean isLogin() {
return SecurityUtils.getSubject().getPrincipal() != null; return SecurityUtils.getSubject().getPrincipal()!= null;
} }
/**
*
* ShiroSecurityUtilsSubjectlogout
* 使
*/
public static void logout() { public static void logout() {
SecurityUtils.getSubject().logout(); SecurityUtils.getSubject().logout();
} }

@ -1,172 +1,224 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" <project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <!--
<groupId>com.tamguo</groupId> project是整个Maven项目对象模型POM的根元素这里通过xmlns等属性定义了命名空间相关信息
<artifactId>tamguo</artifactId> 用于遵循Maven项目配置的规范使得解析器能正确解析各个标签和属性。
<version>V1.0.1</version> -->
<name>tamguo</name> <modelVersion>4.0.0</modelVersion>
<!-- modelVersion指定了当前POM文件所遵循的版本规范这里的4.0.0是常用的Maven POM版本号。 -->
<parent> <groupId>com.tamguo</groupId>
<groupId>org.springframework.boot</groupId> <!-- groupId定义了项目所属的组织或团队的唯一标识符通常是按照反向域名的方式来命名用于在仓库中对项目进行分组管理。 -->
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>tamguo</artifactId>
<version>1.5.3.RELEASE</version> <!-- artifactId是项目的唯一标识符与groupId一起构成了项目在Maven仓库中的坐标用于区分不同的项目构件。 -->
<relativePath /> <!-- lookup parent from repository --> <version>V1.0.1</version>
</parent> <!-- version指定了项目的版本号方便进行版本控制和发布管理不同版本可以有不同的功能、修复等变更。 -->
<name>tamguo</name>
<!-- name属性简单地给项目取了一个更易读的名称便于人类识别项目。 -->
<properties> <parent>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <!-- parent元素用于指定当前项目继承自哪个父项目这样可以继承父项目的配置减少重复配置的工作量。 -->
<java.version>1.8</java.version> <groupId>org.springframework.boot</groupId>
<mybatis-plus-boot-starter.version>2.1.9</mybatis-plus-boot-starter.version> <artifactId>spring-boot-starter-parent</artifactId>
</properties> <version>1.5.3.RELEASE</version>
<!-- 这里指定了父项目的版本号,决定了继承过来的相关依赖等配置的版本情况。 -->
<relativePath /> <!-- lookup parent from repository -->
<!-- relativePath元素在这里表示相对路径若为空此处则会从本地仓库或远程仓库查找父项目。 -->
</parent>
<dependencies> <properties>
<dependency> <!-- properties元素用于定义项目中的一些自定义属性方便在其他地方引用提高配置的可维护性。 -->
<groupId>org.springframework.boot</groupId> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<artifactId>spring-boot-starter-web</artifactId> <!-- 定义项目构建时的源文件编码格式为UTF-8确保代码中的字符能正确解析和处理。 -->
</dependency> <java.version>1.8</java.version>
<dependency> <!-- 指定项目使用的Java版本为1.8,构建和运行项目时会依据此版本进行相关的编译等操作。 -->
<groupId>org.springframework.cloud</groupId> <mybatis-plus-boot-starter.version>2.1.9</mybatis-plus-boot-starter.version>
<artifactId>spring-cloud-starter-config</artifactId> <!-- 自定义的属性用于指定mybatis-plus-boot-starter依赖的版本号方便在后续依赖配置中引用。 -->
</dependency> </properties>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mybatis-plus begin -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- mybatis-plus end -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.32</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.5</version>
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>1.2.1</version>
</dependency>
<dependency>
<groupId>cn.songxinqiang</groupId>
<artifactId>com.baidu.ueditor</artifactId>
<version>1.1.2-edit-1.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.2.8</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
</dependencies>
<dependencyManagement> <dependencies>
<dependencies> <!-- dependencies元素用于列出项目所依赖的其他库或模块这些依赖会被包含到项目的构建和运行环境中。 -->
<dependency> <dependency>
<groupId>org.springframework.cloud</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-cloud-dependencies</artifactId> <artifactId>spring-boot-starter-web</artifactId>
<version>Camden.SR6</version> <!-- 引入Spring Boot的Web启动器依赖它会自动包含构建Web应用所需的一系列基础依赖如Spring MVC等相关库。 -->
<type>pom</type> </dependency>
<scope>import</scope> <dependency>
</dependency> <groupId>org.springframework.cloud</groupId>
</dependencies> <artifactId>spring-cloud-starter-config</artifactId>
</dependencyManagement> <!-- 引入Spring Cloud配置相关的启动器依赖用于实现配置管理等功能比如从配置中心获取配置信息等。 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<!-- 引入Thymeleaf模板引擎的启动器依赖方便在Spring Boot项目中使用Thymeleaf进行页面模板渲染。 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<!-- 引入Spring Boot的JDBC启动器依赖提供了与数据库进行JDBC操作的基础支持可用于连接数据库等操作。 -->
</dependency>
<!-- mybatis-plus begin -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus-boot-starter.version}</version>
<!-- 使用前面定义的属性来指定mybatis-plus-boot-starter的版本号实现依赖版本的统一管理和灵活配置。 -->
<exclusions>
<exclusion>
<artifactId>tomcat-jdbc</artifactId>
<groupId>org.apache.tomcat</groupId>
<!-- exclusions元素用于排除该依赖传递引入的某些子依赖这里排除了tomcat-jdbc依赖可能是因为项目中不需要或者要使用其他的JDBC实现。 -->
</exclusion>
</exclusions>
</dependency>
<!-- mybatis-plus end -->
<dependency>
<groupId>net.sourceforge.nekohtml</groupId>
<artifactId>nekohtml</artifactId>
<!-- 引入nekohtml库可能用于HTML相关的解析或处理等功能具体取决于项目需求。 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<!-- 引入Spring Boot的测试启动器依赖scope属性设置为test表示该依赖仅在测试阶段使用不会包含到最终的生产环境部署包中。 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>1.3.8.RELEASE</version>
<!-- 引入Spring Boot的Redis启动器依赖指定了特定的版本号用于在项目中集成Redis进行缓存、消息队列等相关功能这里版本号是当时配置时选择的旧版本1.3.8.RELEASE-->
</dependency>
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
<!-- 引入Spring Session与Redis集成相关的依赖用于实现基于Redis的会话管理等功能。 -->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.32</version>
<!-- 引入阿里巴巴的FastJSON库用于JSON数据的序列化和反序列化操作方便在项目中处理JSON格式的数据。 -->
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.2.5</version>
<!-- 引入Apache Shiro与Spring集成的依赖用于实现安全认证、授权等功能版本号为1.2.5。 -->
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.5</version>
<!-- 引入Apache Shiro与Ehcache集成的依赖可能用于缓存Shiro相关的一些数据如授权信息等版本号与前面Shiro相关依赖一致为1.2.5。 -->
</dependency>
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>1.2.1</version>
<!-- 引入用于在Thymeleaf模板中集成Shiro功能的扩展依赖方便在页面模板中进行权限相关的控制等操作版本号为1.2.1。 -->
</dependency>
<dependency>
<groupId>cn.songxinqiang</groupId>
<artifactId>com.baidu.ueditor</artifactId>
<version>1.1.2-edit-1.0</version>
<!-- 引入百度UEditor相关的依赖可能用于在项目中实现富文本编辑器等功能具体版本号为1.1.2-edit-1.0。 -->
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<!-- 引入Apache Commons Codec库提供了一些常用的编码解码相关的工具类比如Base64编码等功能。 -->
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
<!-- 引入Apache Commons FileUpload库用于处理文件上传功能指定了版本号为1.3.1。 -->
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<!-- 引入Apache Commons IO库提供了很多方便的IO操作相关的工具类用于文件、流等操作。 -->
</dependency>
<dependency>
<groupId>com.github.penggle</groupId>
<artifactId>kaptcha</artifactId>
<version>2.3.2</version>
<exclusions>
<exclusion>
<artifactId>javax.servlet-api</artifactId>
<groupId>javax.servlet</groupId>
<!-- 排除该依赖传递引入的javax.servlet-api依赖可能是项目中已经有其他方式管理了这个依赖或者不需要该版本的此依赖。 -->
</exclusion>
</exclusions>
<!-- 引入用于生成验证码的Kaptcha库版本号为2.3.2。 -->
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.18</version>
<!-- 引入阿里巴巴的Druid数据库连接池依赖用于管理数据库连接提高连接的性能和复用等版本号为1.0.18。 -->
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!-- 引入MySQL的Java驱动依赖用于在项目中连接MySQL数据库进行数据交互操作。 -->
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
<!-- 引入Apache Commons Lang3库提供了很多常用的工具方法用于字符串、日期、对象等操作版本号为3.6。 -->
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-dysmsapi</artifactId>
<version>1.0.0</version>
<!-- 引入阿里云短信服务Dysmsapi的Java SDK依赖用于在项目中实现发送短信等相关功能版本号为1.0.0。 -->
</dependency>
<dependency>
<groupId>com.aliyun</groupId>
<artifactId>aliyun-java-sdk-core</artifactId>
<version>3.2.8</version>
<!-- 引入阿里云Java SDK的核心依赖其他阿里云相关的服务SDK可能依赖于此核心库版本号为3.2.8。 -->
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
<!-- 引入Apache Commons Email库用于在项目中实现发送电子邮件等相关功能版本号为1.5。 -->
</dependency>
</dependencies>
<build> <dependencyManagement>
<finalName>tamguo</finalName> <!-- dependencyManagement元素用于管理项目依赖的版本它不会实际引入依赖而是定义了依赖的版本号等信息
<plugins> 供子项目或者当前项目中依赖声明时使用,起到统一管理依赖版本的作用。 -->
<plugin> <dependencies>
<groupId>org.springframework.boot</groupId> <dependency>
<artifactId>spring-boot-maven-plugin</artifactId> <groupId>org.springframework.cloud</groupId>
</plugin> <artifactId>spring-cloud-dependencies</artifactId>
</plugins> <version>Camden.SR6</version>
</build> <type>pom</type>
<scope>import</scope>
<!-- 引入Spring Cloud的依赖管理配置通过import作用域将指定版本Camden.SR6的Spring Cloud相关依赖的管理配置引入到当前项目中
这样项目中再引入Spring Cloud相关依赖时就可以不用再指定版本号了遵循这里定义的版本管理。 -->
</dependency>
</dependencies>
</dependencyManagement>
<build>
<!-- build元素用于配置项目的构建相关信息比如构建插件、最终生成的项目名称等。 -->
<finalName>tamguo</finalName>
<!-- 指定项目最终构建生成的文件名不包含扩展名这里设置为tamguo最终可能生成类似tamguo.jar或者tamguo.war等文件。 -->
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<!-- 引入Spring Boot的Maven插件用于将Spring Boot项目进行打包、运行等构建操作是Spring Boot项目构建的关键插件。 -->
</plugin>
</plugins>
</build>
</project> </project>

@ -19,51 +19,93 @@ import com.google.code.kaptcha.util.Config;
import com.tamguo.interceptor.MemberInterceptor; import com.tamguo.interceptor.MemberInterceptor;
import com.tamguo.interceptor.MenuInterceptor; import com.tamguo.interceptor.MenuInterceptor;
// 这个类使用了Spring的 @Configuration 注解表示它是一个配置类用于配置Spring Web相关的一些功能和组件
// Spring在启动时会自动扫描并加载该类中的配置信息将其中定义的Bean注册到Spring容器中。
@Configuration @Configuration
public class WebConfig extends WebMvcConfigurerAdapter { public class WebConfig extends WebMvcConfigurerAdapter {
@Value("${file.storage.path}") // 通过 @Value 注解从配置文件(例如 application.properties 或 application.yml中读取名为 "file.storage.path" 的属性值,
private String fileStoragePath; // 并注入到这个变量中,该路径可能用于指定文件存储的位置,后续在资源处理器配置中会用到这个路径来映射资源访问路径。
@Autowired @Value("${file.storage.path}")
private MenuInterceptor menuInterceptor; private String fileStoragePath;
@Autowired
private MemberInterceptor memberInterceptor; // 通过Spring的依赖注入机制自动注入 MenuInterceptor 实例MenuInterceptor 应该是自定义的拦截器,
// 用于在请求处理过程中对菜单相关的逻辑进行拦截和处理,比如权限校验、菜单数据准备等操作(具体功能取决于拦截器的实现代码)。
@Autowired
private MenuInterceptor menuInterceptor;
// 同样通过依赖注入机制注入 MemberInterceptor 实例MemberInterceptor 也是自定义拦截器,
// 从命名可以推测它可能用于对会员相关的请求路径进行拦截,执行如会员权限验证、会员数据处理等相关操作。
@Autowired
private MemberInterceptor memberInterceptor;
/** /**
* *
*/ * WebMvcConfigurerAdapter addInterceptors Spring
@Override * Web
public void addInterceptors(InterceptorRegistry registry) { */
registry.addInterceptor(menuInterceptor).addPathPatterns("/**"); @Override
registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**"); public void addInterceptors(InterceptorRegistry registry) {
} // 将 menuInterceptor 注册到拦截器注册表中,并配置其拦截的路径模式为 "/**",表示对所有的请求路径都会进行拦截,
// 具体在 MenuInterceptor 内部会根据业务逻辑判断是否需要进行处理以及如何处理这些请求。
registry.addInterceptor(menuInterceptor).addPathPatterns("/**");
// 将 memberInterceptor 注册到拦截器注册表中,配置其拦截的路径模式为 "/member/**",意味着对以 "/member/" 开头的请求路径进行拦截,
// 这通常用于对会员模块相关的请求进行特定的前置处理,比如验证会员登录状态、权限等情况。
registry.addInterceptor(memberInterceptor).addPathPatterns("/member/**");
}
@Override /**
public void addResourceHandlers(ResourceHandlerRegistry registry) { *
registry.addResourceHandler("/files/**").addResourceLocations("file:"+fileStoragePath); * addResourceHandlers Spring Web
super.addResourceHandlers(registry); * "/files/**" fileStoragePath
} * "/files/" Spring
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**").addResourceLocations("file:" + fileStoragePath);
super.addResourceHandlers(registry);
}
@Bean(name="producer") /**
public DefaultKaptcha getKaptchaBean(){ * Kaptcha
DefaultKaptcha defaultKaptcha=new DefaultKaptcha(); * "producer" Bean DefaultKaptcha DefaultKaptcha
Properties properties=new Properties(); * Properties
// properties.setProperty("kaptcha.border.color", "105,179,90"); * DefaultKaptcha 便使
*/
@Bean(name = "producer")
public DefaultKaptcha getKaptchaBean() {
DefaultKaptcha defaultKaptcha = new DefaultKaptcha();
Properties properties = new Properties();
// properties.setProperty("kaptcha.border.color", "105,179,90");
// 设置验证码图片是否显示边框,这里配置为 "no",即不显示边框。
properties.setProperty("kaptcha.border", "no"); properties.setProperty("kaptcha.border", "no");
// 设置验证码图片的宽度为 125 像素。
properties.setProperty("kaptcha.image.width", "125"); properties.setProperty("kaptcha.image.width", "125");
// 设置验证码图片的高度为 45 像素。
properties.setProperty("kaptcha.image.height", "45"); properties.setProperty("kaptcha.image.height", "45");
// 设置在Session中存储验证码文本的键名这里设置为 "code"方便后续在验证时从Session中获取对应的验证码文本进行比对验证。
properties.setProperty("kaptcha.session.key", "code"); properties.setProperty("kaptcha.session.key", "code");
// 设置验证码文本的字符长度为 4 个字符,即生成的验证码文本将包含 4 个字符。
properties.setProperty("kaptcha.textproducer.char.length", "4"); properties.setProperty("kaptcha.textproducer.char.length", "4");
// 设置验证码文本生成时使用的字体,可以指定多个字体,用逗号隔开,这里指定了宋体、楷体、微软雅黑等常见字体,
// 在生成验证码时会随机从这些字体中选择一种来绘制验证码文本。
properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑"); properties.setProperty("kaptcha.textproducer.font.names", "宋体,楷体,微软雅黑");
Config config=new Config(properties); Config config = new Config(properties);
defaultKaptcha.setConfig(config); defaultKaptcha.setConfig(config);
return defaultKaptcha; return defaultKaptcha;
} }
@Bean /**
* Servlet
* "containerCustomizer" Bean EmbeddedServletContainerCustomizer
* ServletSpring Boot使Tomcat
* HttpStatus.NOT_FOUND404 "/404.html"
* HttpStatus.INTERNAL_SERVER_ERROR500 "/500.html"
*
*/
@Bean
public EmbeddedServletContainerCustomizer containerCustomizer() { public EmbeddedServletContainerCustomizer containerCustomizer() {
return new EmbeddedServletContainerCustomizer(){ return new EmbeddedServletContainerCustomizer() {
@Override @Override
public void customize(ConfigurableEmbeddedServletContainer container) { public void customize(ConfigurableEmbeddedServletContainer container) {
container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html")); container.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
@ -71,5 +113,4 @@ public class WebConfig extends WebMvcConfigurerAdapter {
} }
}; };
} }
} }

@ -5,22 +5,37 @@ import org.springframework.context.annotation.Bean;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;
//这个类用配置redis服务器的连接 // 这个类用于配置Redis服务器的连接它是整个项目中与Redis会话相关配置的一部分
@EnableRedisHttpSession(maxInactiveIntervalInSeconds= 1800) @EnableRedisHttpSession(maxInactiveIntervalInSeconds = 1800)
// @EnableRedisHttpSession注解用于启用Spring Session基于Redis的HTTP会话管理功能
// maxInactiveIntervalInSeconds属性设置了会话的最大非活动时间间隔这里设置为1800秒即30分钟
// 意味着如果一个会话在30分钟内没有任何活动将会被认为过期并失效。
public class SessionConfig { public class SessionConfig {
// 使用@Value注解从配置文件例如application.properties或application.yml中读取名为"redis.hostname"的属性值,
// 并将其注入到这个变量中用于后续设置Redis连接的主机名。
@Value("${redis.hostname}") @Value("${redis.hostname}")
String HostName; String HostName;
// 同样通过@Value注解从配置文件中读取"redis.port"属性值注入到这个变量用于设置Redis连接的端口号。
@Value("${redis.port}") @Value("${redis.port}")
int Port; int Port;
// 从配置文件读取"redis.password"属性值注入此变量用于设置连接Redis服务器时的密码如果有设置密码的话
@Value("${redis.password}") @Value("${redis.password}")
String password; String password;
// 使用@Bean注解将这个方法返回的对象注册为Spring容器中的一个Bean
// 这个Bean就是JedisConnectionFactory类型用于创建与Redis服务器的连接。
@Bean @Bean
public JedisConnectionFactory connectionFactory() { public JedisConnectionFactory connectionFactory() {
JedisConnectionFactory connection = new JedisConnectionFactory(); JedisConnectionFactory connection = new JedisConnectionFactory();
// 设置JedisConnectionFactory的端口号使用前面通过@Value注入获取到的Port变量的值
// 以此来确定连接Redis服务器的具体端口。
connection.setPort(Port); connection.setPort(Port);
// 设置JedisConnectionFactory的主机名使用注入的HostName变量值明确要连接的Redis服务器主机地址。
connection.setHostName(HostName); connection.setHostName(HostName);
// 设置连接Redis服务器的密码使用password变量值确保连接的安全性如果Redis服务器设置了密码验证的话
connection.setPassword(password); connection.setPassword(password);
return connection; return connection;
} }

@ -21,20 +21,28 @@ import com.tamguo.service.IMemberService;
/** /**
* *
* * `MemberRealm` `AuthorizingRealm` Apache Shiro
*
*/ */
public class MemberRealm extends AuthorizingRealm { public class MemberRealm extends AuthorizingRealm {
// 通过Spring的依赖注入机制自动注入 `IMemberService` 接口的实现类实例,
// 用于后续在认证和授权过程中与数据库等数据源交互,获取用户相关信息(如用户实体、登录失败次数等)。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/** /**
* () * ()
*/ * `AuthorizingRealm` `doGetAuthorizationInfo`
* Shiro `PrincipalCollection`
* `SimpleAuthorizationInfo`
*/
@Override @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
Set<String > permsSet = null; // 初始化一个用于存储权限字符串的集合,当前示例中暂未填充具体的权限数据,实际业务中需要从数据库等数据源获取用户对应的权限并添加到这个集合中。
Set<String> permsSet = null;
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 将权限集合设置到 `SimpleAuthorizationInfo` 对象中,用于后续权限验证时判断用户是否具有特定权限。
info.setStringPermissions(permsSet); info.setStringPermissions(permsSet);
return info; return info;
@ -42,32 +50,51 @@ public class MemberRealm extends AuthorizingRealm {
/** /**
* () * ()
* `AuthorizingRealm` `doGetAuthenticationInfo`
* Shiro
*
*/ */
@Override @Override
protected AuthenticationInfo doGetAuthenticationInfo( protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException { AuthenticationToken token) throws AuthenticationException {
// 从 `AuthenticationToken` 中获取用户输入的用户名,在 Shiro 中,`AuthenticationToken` 通常是在登录时传递用户名和密码等登录凭证的对象,
// 这里将获取到的用户名强制转换为 `String` 类型,因为假设登录时用户名是以字符串形式传递的(实际应用中需根据具体的 `AuthenticationToken` 实现来确定类型转换是否正确)。
String username = (String) token.getPrincipal(); String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials()); // 从 `AuthenticationToken` 中获取用户输入的密码,同样需要进行类型转换,将其转换为 `String` 类型,这里将 `char[]` 类型的密码转换为 `String`
// 注意这种方式在实际应用中可能存在安全风险(比如密码在内存中会以明文形式存在一段时间),更安全的做法是使用合适的加密算法立即对密码进行处理,避免明文暴露。
String password = new String((char[]) token.getCredentials());
MemberEntity member = iMemberService.findByUsername(username); // 通过注入的 `IMemberService` 调用 `findByUsername` 方法,根据用户名从数据库或其他数据源查找对应的 `MemberEntity`(用户实体对象),
if(member == null) { // 如果返回 `null`,说明用户名不存在,此时抛出 `UnknownAccountException`,表示用户名或密码有误(在实际应用中可以更细化错误提示,区分是用户名不存在还是密码错误等情况)。
throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码"); MemberEntity member = iMemberService.findByUsername(username);
} if (member == null) {
Integer loginFailureCount = iMemberService.getLoginFailureCount(member); throw new UnknownAccountException("用户名或密码有误,请重新输入或找回密码");
if(loginFailureCount > 10) { }
throw new LockedAccountException("账号被锁定");
}
if(!new Sha256Hash(password).toHex().equals(member.getPassword())){ // 通过 `IMemberService` 调用 `getLoginFailureCount` 方法获取当前用户的登录失败次数,
// 如果登录失败次数大于 10 次,说明账号可能存在异常风险,抛出 `LockedAccountException`,表示账号被锁定,禁止登录。
Integer loginFailureCount = iMemberService.getLoginFailureCount(member);
if (loginFailureCount > 10) {
throw new LockedAccountException("账号被锁定");
}
// 使用 `Sha256Hash` 对用户输入的密码进行哈希处理(将密码转换为不可逆的哈希值),并与数据库中存储的用户密码(假设数据库中存储的也是经过相同哈希算法处理后的哈希值)进行比较,
// 如果不相等,说明密码错误,此时增加登录失败次数,并通过 `IMemberService` 的 `updateLoginFailureCount` 方法更新数据库中的登录失败次数记录,
// 然后抛出 `IncorrectCredentialsException`,提示用户名或密码有误,请重新输入或找回密码。
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` 方法更新用户的最后登录时间,记录此次登录操作,
// 然后创建一个 `SimpleAuthenticationInfo` 对象,将用户实体对象(`member`)、密码(`password`)以及当前 `Realm` 的名称(通过 `getName()` 获取)传递进去,
// 这个对象会被 Shiro 框架用于后续的认证相关流程,比如在会话中保存认证信息等操作。
// 更新登录时间 // 更新登录时间
iMemberService.updateLastLoginTime(member.getUid().toString()); iMemberService.updateLastLoginTime(member.getUid().toString());
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName()); SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(member, password, getName());
return info; return info;
} }
} }

@ -14,15 +14,26 @@ import org.springframework.context.annotation.Configuration;
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
// 这个类使用了Spring的 @Configuration 注解表明它是一个配置类用于配置Shiro框架相关的各种组件和功能
// Spring在启动时会自动扫描并加载这个类中的配置信息将其中定义的Bean注册到Spring容器中。
@Configuration @Configuration
public class ShiroConfiguration { public class ShiroConfiguration {
// 创建一个LinkedHashMap用于定义Shiro的过滤链规则键是URL路径的匹配模式值是对应的权限过滤器名称
// 后续会将这个映射关系配置到ShiroFilterFactoryBean中用于控制不同URL的访问权限。
private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>(); private static Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 定义一个名为 "shiroRealm" 的Bean这个Bean返回的是自定义的MemberRealm实例
// MemberRealm是继承自Shiro的AuthorizingRealm用于实现具体的认证登录验证和授权权限验证逻辑
// 将其注册为Spring容器中的Bean方便其他Shiro相关组件引用比如在SecurityManager中设置使用这个Realm进行验证操作。
@Bean(name = "shiroRealm") @Bean(name = "shiroRealm")
public MemberRealm getShiroRealm() { public MemberRealm getShiroRealm() {
return new MemberRealm(); return new MemberRealm();
} }
// 定义一个名为 "shiroEhcacheManager" 的Bean用于创建和配置EhCacheManager实例
// EhCacheManager在Shiro框架中用于缓存相关的数据比如用户的授权信息等提高系统性能减少重复查询授权等操作的开销。
// 通过设置cacheManagerConfigFile属性指定了EhCache的配置文件路径这里是类路径下的ehcache-shiro.xml文件
// 该配置文件会定义缓存的具体策略、缓存区域等相关设置。
@Bean(name = "shiroEhcacheManager") @Bean(name = "shiroEhcacheManager")
public EhCacheManager getEhCacheManager() { public EhCacheManager getEhCacheManager() {
EhCacheManager em = new EhCacheManager(); EhCacheManager em = new EhCacheManager();
@ -30,11 +41,17 @@ public class ShiroConfiguration {
return em; return em;
} }
// 定义一个名为 "lifecycleBeanPostProcessor" 的Bean创建LifecycleBeanPostProcessor实例
// LifecycleBeanPostProcessor是Shiro提供的一个用于管理Shiro相关Bean生命周期的后置处理器
// 它能够确保Shiro的一些组件如Realm等在Spring容器中正确地初始化、销毁等按照其生命周期规范执行相应操作。
@Bean(name = "lifecycleBeanPostProcessor") @Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { public LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor(); return new LifecycleBeanPostProcessor();
} }
// 定义一个名为 "defaultAdvisorAutoProxyCreator" 的Bean创建DefaultAdvisorAutoProxyCreator实例
// 它是Spring AOP中的一个自动代理创建器用于在Spring容器中自动创建基于Advisor切面顾问的代理对象
// 在Shiro与Spring集成时帮助实现基于AOP的权限控制拦截等功能通过设置proxyTargetClass为true表示使用基于类的代理方式而不是基于接口的代理
@Bean @Bean
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
@ -42,6 +59,10 @@ public class ShiroConfiguration {
return daap; return daap;
} }
// 定义一个名为 "securityManager" 的Bean创建DefaultWebSecurityManager实例
// DefaultWebSecurityManager是Shiro在Web应用中用于管理安全相关操作的核心组件
// 在这里设置了它所使用的Realm通过调用getShiroRealm()方法获取前面定义的MemberRealm实例以及缓存管理器通过getEhCacheManager()获取EhCacheManager实例
// 这样SecurityManager在进行认证和授权操作时就会使用指定的Realm进行验证并利用缓存管理器来缓存相关信息提高效率。
@Bean(name = "securityManager") @Bean(name = "securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager() { public DefaultWebSecurityManager getDefaultWebSecurityManager() {
DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager(); DefaultWebSecurityManager dwsm = new DefaultWebSecurityManager();
@ -50,6 +71,10 @@ public class ShiroConfiguration {
return dwsm; return dwsm;
} }
// 定义一个名为 "authorizationAttributeSourceAdvisor" 的Bean创建AuthorizationAttributeSourceAdvisor实例
// AuthorizationAttributeSourceAdvisor是Shiro与Spring AOP集成的一个关键组件用于在方法级别进行权限控制
// 它通过设置securityManager调用getDefaultWebSecurityManager()获取配置好的SecurityManager来关联整个权限管理体系
// 使得在Spring应用中可以基于注解等方式方便地进行权限控制比如在方法上添加Shiro的权限注解来限制哪些用户可以访问该方法。
@Bean @Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() { public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
@ -59,13 +84,23 @@ public class ShiroConfiguration {
/** /**
* ShiroDialectthymeleaf使shirobean * ShiroDialectthymeleaf使shirobean
* @return * "shiroDialect" BeanShiroDialect
* ShiroDialectThymeleaf使Shiro
* 使ThymeleafShiro
* BeanSpringThymeleafShiro
*/ */
@Bean @Bean
public ShiroDialect shiroDialect(){ public ShiroDialect shiroDialect() {
return new ShiroDialect(); return new ShiroDialect();
} }
// 定义一个名为 "shiroFilter" 的Bean创建ShiroFilterFactoryBean实例
// ShiroFilterFactoryBean是Shiro在Web应用中用于配置URL级别的访问控制过滤器链的核心组件
// 它通过设置securityManager关联前面配置好的DefaultWebSecurityManager来建立整个权限过滤的基础框架
// 同时设置了登录页面的URL通过setLoginUrl方法设置为 "/login",当用户未登录访问受保护资源时会重定向到这个登录页面)、
// 登录成功后的默认跳转页面URL通过setSuccessUrl方法设置为 "/index"
// 并且将前面定义的filterChainDefinitionMap包含了URL路径和对应权限过滤器的映射关系设置进去
// 这样Shiro就能根据这些配置对不同的URL请求进行权限验证和相应的处理比如哪些URL需要认证才能访问哪些URL可以匿名访问等。
@Bean(name = "shiroFilter") @Bean(name = "shiroFilter")
public ShiroFilterFactoryBean getShiroFilterFactoryBean() { public ShiroFilterFactoryBean getShiroFilterFactoryBean() {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

@ -6,12 +6,22 @@ import org.apache.ibatis.annotations.Param;
import com.tamguo.config.dao.SuperMapper; import com.tamguo.config.dao.SuperMapper;
import com.tamguo.model.ChapterEntity; import com.tamguo.model.ChapterEntity;
public interface ChapterMapper extends SuperMapper<ChapterEntity>{ // 定义了一个名为ChapterMapper的接口该接口继承自SuperMapper<ChapterEntity>
// 通常继承自自定义的通用Mapper接口这里的SuperMapper大概率是用于简化MyBatis常见CRUD操作的基础Mapper接口
// 这样可以复用一些通用的数据库操作方法比如增删改查等基本功能同时也可以定义自己特定的查询方法用于与数据库中的章节Chapter相关表进行交互。
public interface ChapterMapper extends SuperMapper<ChapterEntity> {
List<ChapterEntity> findByBookId(@Param(value="bookId") String bookId); // 定义了一个查询方法findByBookId用于根据书籍的唯一标识符bookId从数据库中查找对应的章节信息列表。
// 使用了@Param注解来为方法参数指定一个名称这样在对应的SQL语句中就可以通过这个指定的名称来引用参数
// 在这里参数名为"bookId"表示传入的是书籍的ID方法返回值是一个ChapterEntity类型的列表即查询到的对应章节实体的集合。
List<ChapterEntity> findByBookId(@Param(value = "bookId") String bookId);
List<ChapterEntity> findByParentId(@Param(value="parentId") String parentId); // 定义另一个查询方法findByParentId目的是根据章节的父级章节的唯一标识符parentId来查找对应的子章节信息列表。
// 同样使用了@Param注解为参数命名为"parentId"方便在SQL语句中引用返回值也是ChapterEntity类型的列表包含了满足条件的子章节实体集合。
ChapterEntity findNextPoint(@Param(value="uid")String uid , @Param(value="orders")Integer orders); List<ChapterEntity> findByParentId(@Param(value = "parentId") String parentId);
// 定义了一个查找下一个知识点这里推测ChapterEntity可能代表知识点相关实体具体取决于业务逻辑的方法findNextPoint。
// 该方法接收两个参数一个是知识点的唯一标识符uid另一个是顺序号orders通过这两个参数从数据库中查找符合条件的下一个知识点实体。
// 使用@Param注解分别为两个参数命名为"uid"和"orders"方便在对应的SQL语句中准确引用参数返回值是一个ChapterEntity类型的对象代表找到的下一个知识点实体。
ChapterEntity findNextPoint(@Param(value = "uid") String uid, @Param(value = "orders") Integer orders);
} }

@ -7,27 +7,54 @@ import com.baomidou.mybatisplus.plugins.pagination.Pagination;
import com.tamguo.config.dao.SuperMapper; import com.tamguo.config.dao.SuperMapper;
import com.tamguo.model.PaperEntity; import com.tamguo.model.PaperEntity;
public interface PaperMapper extends SuperMapper<PaperEntity>{ // 定义了名为PaperMapper的接口它继承自SuperMapper<PaperEntity>
// 与之前类似SuperMapper大概率是一个自定义的通用Mapper接口用于继承通用的数据库操作方法如基本的增删改查等
List<PaperEntity> findByTypeAndAreaId(@Param(value="type")String type, @Param(value="areaId")String areaId , Pagination page); // 在此基础上PaperMapper接口可以定义针对试卷PaperEntity从命名推测其代表试卷相关实体相关的特定数据库查询方法。
public interface PaperMapper extends SuperMapper<PaperEntity> {
List<PaperEntity> findByAreaId(@Param(value="areaId") String areaId , Pagination page);
// 定义一个查询方法findByTypeAndAreaId用于根据试卷类型type和地区IDareaId进行分页查询试卷信息列表。
List<PaperEntity> findBySchoolId(@Param(value="schoolId")String schoolId , Pagination page); // 使用了@Param注解为方法的前两个参数分别命名为"type"和"areaId"方便在对应的SQL语句中准确引用参数
// 第三个参数是Pagination类型的page这是MyBatis Plus提供的用于分页的对象通过它可以指定分页相关的参数如页码、每页数量等
List<PaperEntity> findList(@Param(value="subjectId")String subjectId, @Param(value="courseId")String courseId, @Param(value="paperType")String paperType, // 方法返回值是一个PaperEntity类型的列表即符合条件的试卷实体集合。
@Param(value="year")String year, @Param(value="area")String area , Pagination page); List<PaperEntity> findByTypeAndAreaId(@Param(value = "type") String type, @Param(value = "areaId") String areaId, Pagination page);
List<PaperEntity> findPaperByAreaId(@Param(value="areaId")String areaId , @Param(value="type")String type , Pagination page); // 定义查询方法findByAreaId根据地区IDareaId进行分页查询试卷信息列表。
// 使用@Param注解为参数"areaId"命名便于SQL语句引用同样传入Pagination类型的page对象用于分页
// 返回值为PaperEntity类型的列表包含了该地区下对应的试卷实体集合。
List<PaperEntity> findByAreaId(@Param(value = "areaId") String areaId, Pagination page);
// 定义查询方法findBySchoolId依据学校IDschoolId进行分页查询试卷信息列表。
// 通过@Param注解将参数命名为"schoolId"结合分页对象page来实现分页查询功能返回符合条件的试卷实体列表。
List<PaperEntity> findBySchoolId(@Param(value = "schoolId") String schoolId, Pagination page);
// 定义了一个较为复杂的查询方法findList用于根据多个条件进行分页查询试卷信息列表这些条件包括学科IDsubjectId、课程IDcourseId
// 试卷类型paperType、年份year以及地区area通过@Param注解分别为这些参数命名方便在SQL语句中准确引用
// 传入Pagination类型的page对象用于分页操作返回值是满足所有条件的PaperEntity类型的试卷实体列表。
List<PaperEntity> findList(@Param(value = "subjectId") String subjectId, @Param(value = "courseId") String courseId,
@Param(value = "paperType") String paperType,
@Param(value = "year") String year, @Param(value = "area") String area, Pagination page);
// 定义查询方法findPaperByAreaId根据地区IDareaId和试卷类型type进行分页查询试卷信息列表。
// 利用@Param注解对两个参数"areaId"和"type"进行命名结合分页对象page实现分页查询返回符合条件的试卷实体集合。
List<PaperEntity> findPaperByAreaId(@Param(value = "areaId") String areaId, @Param(value = "type") String type, Pagination page);
// 定义了一个查询方法getPaperTotal用于获取试卷的总数量。
// 该方法没有传入分页相关的参数返回值是Long类型表示符合某种未明确限定可能是所有记录也可能是满足特定默认条件的记录取决于具体业务逻辑和SQL实现的试卷总条数。
Long getPaperTotal(); Long getPaperTotal();
List<PaperEntity> findByCreaterId(@Param(value="createrId")String createrId); // 定义查询方法findByCreaterId根据创建者的IDcreaterId来查找对应的试卷信息列表。
// 使用@Param注解将参数命名为"createrId"方便在SQL语句中引用返回值为PaperEntity类型的列表即该创建者创建的试卷实体集合。
List<PaperEntity> queryPageByNameAndCreatorId(@Param(value="name")String name, @Param(value="memberId")String memberId , Pagination page); List<PaperEntity> findByCreaterId(@Param(value = "createrId") String createrId);
List<PaperEntity> featuredPaper(@Param(value="type")String type, @Param(value="subjectId")String subjectId , Pagination page); // 定义查询方法queryPageByNameAndCreatorId用于根据试卷名称name和创建者IDmemberId进行分页查询试卷信息列表。
// 通过@Param注解分别为两个参数命名传入Pagination类型的page对象用于分页返回值是满足条件的试卷实体集合。
List<PaperEntity> queryPageByNameAndCreatorId(@Param(value = "name") String name, @Param(value = "memberId") String memberId, Pagination page);
List<PaperEntity> findHotPaper(@Param(value="subjectId")String subjectId,@Param(value="courseId") String courseId, Pagination page); // 定义查询方法featuredPaper根据试卷类型type和学科IDsubjectId进行分页查询特色试卷信息列表。
// 使用@Param注解为参数命名结合分页对象page实现分页查询功能返回值是符合特色试卷相关条件的PaperEntity类型的列表。
List<PaperEntity> featuredPaper(@Param(value = "type") String type, @Param(value = "subjectId") String subjectId, Pagination page);
// 定义查询方法findHotPaper根据学科IDsubjectId和课程IDcourseId进行分页查询热门试卷信息列表。
// 利用@Param注解对两个参数命名结合分页对象page进行分页查询操作返回值是满足热门试卷相关条件的PaperEntity类型的列表。
List<PaperEntity> findHotPaper(@Param(value = "subjectId") String subjectId, @Param(value = "courseId") String courseId, Pagination page);
} }

@ -6,8 +6,10 @@ import javax.servlet.http.HttpServletResponse;
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在扫描时能够发现并将其纳入到Spring容器管理中
// 便于在其他地方进行依赖注入或者配置使用。
@Component @Component
public class MemberInterceptor extends HandlerInterceptorAdapter{ public class MemberInterceptor extends HandlerInterceptorAdapter {
/** "重定向URL"参数名称 */ /** "重定向URL"参数名称 */
private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl"; private static final String REDIRECT_URL_PARAMETER_NAME = "redirectUrl";
@ -20,36 +22,47 @@ public class MemberInterceptor extends HandlerInterceptorAdapter{
/** /**
* *
* HandlerInterceptorAdapterpreHandleController
*
* *
* @param request * @param request HttpServletRequestHTTP
* HttpServletRequest * @param response HttpServletResponse
* @param response * @param handler Controller
* HttpServletResponse * @return trueControllerfalse
* @param handler
*
* @return
*/ */
@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"); // 从当前的HTTP请求对象中获取会话Session里名为"currMember"的属性值,通常这里的"currMember"可能是存储了当前已登录会员的相关信息的对象,
if (currMember != null) { // 如果获取到的值不为null说明当前用户已经登录那么允许请求继续执行直接返回true。
Object currMember = request.getSession().getAttribute("currMember");
if (currMember!= null) {
return true; return true;
} else { } else {
// 获取请求头中名为"X-Requested-With"的值这个请求头常用于判断请求是否是通过AjaxXMLHttpRequest发起的
// 在一些Web应用中对于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请求"X-Requested-With"请求头的值为"XMLHttpRequest"),则向响应头中添加一个名为"loginStatus"的自定义头信息,
// 设置其值为"accessDenied"表示登录状态为拒绝访问也就是未登录情况下的Ajax请求被拦截了
// 然后设置响应的状态码为HttpServletResponse.SC_FORBIDDEN403表示禁止访问最后返回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方法对于不同的HTTP请求方法重定向的逻辑可能稍有不同。
if (request.getMethod().equalsIgnoreCase("GET")) { if (request.getMethod().equalsIgnoreCase("GET")) {
String redirectUrl = request.getQueryString() != null ? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI(); // 如果是GET请求构建一个重定向的URL将当前请求的完整路径包括请求的URI和查询字符串如果有的话进行编码后作为参数添加到登录URL后面
// 具体做法是先判断当前请求是否有查询字符串通过request.getQueryString()判断如果有则将请求的URI和查询字符串拼接起来
// 如果没有查询字符串就只使用请求的URI然后使用URLEncoder将这个重定向的URL按照UTF-8编码格式进行编码避免出现中文等特殊字符乱码问题
// 最后通过response.sendRedirect方法将客户端重定向到登录页面并带上这个重定向URL参数以便在登录成功后可以根据这个参数跳回原来请求的页面。
String redirectUrl = request.getQueryString()!= null? request.getRequestURI() + "?" + request.getQueryString() : request.getRequestURI();
response.sendRedirect(request.getContextPath() + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8")); response.sendRedirect(request.getContextPath() + loginUrl + "?" + REDIRECT_URL_PARAMETER_NAME + "=" + URLEncoder.encode(redirectUrl, "UTF-8"));
} else { } else {
// 如果不是GET请求比如POST等其他请求方法直接重定向到登录页面不携带额外的重定向URL参数因为对于非GET请求通常不需要记录原请求的详细信息来进行后续跳转。
response.sendRedirect(request.getContextPath() + loginUrl); response.sendRedirect(request.getContextPath() + loginUrl);
} }
return false; return false;
} }
} }
} }
} }

@ -9,51 +9,69 @@ import com.tamguo.config.dao.SuperEntity;
/** /**
* The persistent class for the tiku_ad database table. * The persistent class for the tiku_ad database table.
* * AdEntitytiku_ad广Ad
* SuperEntity<AdEntity>SerializableSuperEntitySuperEntity
* Serializable
*/ */
@TableName(value="tiku_ad") @TableName(value = "tiku_ad")
public class AdEntity extends SuperEntity<AdEntity> implements Serializable { public class AdEntity extends SuperEntity<AdEntity> implements Serializable {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// 用于存储业务相关的键值具体含义可能与广告业务中的某种标识或者关联信息有关其值由外部设置并获取通过对应的getter和setter方法操作。
private String businessKey; private String businessKey;
// 用于存储广告的名称方便在业务中对不同广告进行区分和识别同样通过getter和setter方法进行访问和修改。
private String name; private String name;
// 用于存储广告的详细信息,可能是包含了广告内容、展示规则等各种相关数据的字符串表示形式(具体格式取决于业务设计),
// 通过getter和setter方法来操作该属性值。
private String adInfo; private String adInfo;
// getName方法用于获取广告名称属性的值供外部代码调用遵循JavaBean规范的getter方法定义。
public String getName() { public String getName() {
return name; return name;
} }
// setName方法用于设置广告名称属性的值外部代码可以传入一个字符串参数来更新广告名称遵循JavaBean规范的setter方法定义。
public void setName(String name) { public void setName(String name) {
this.name = name; this.name = name;
} }
// getSerialversionuid方法返回类的序列化版本UID这个UID在序列化和反序列化过程中用于验证类的版本一致性
// 这里直接返回了定义好的静态常量serialVersionUID的值一般不需要手动修改这个方法的实现它是按照Java序列化机制要求定义的。
public static long getSerialversionuid() { public static long getSerialversionuid() {
return serialVersionUID; return serialVersionUID;
} }
// getAdInfo方法用于获取广告详细信息adInfo属性的值外部代码可以通过调用这个方法获取存储的广告相关信息字符串。
public String getAdInfo() { public String getAdInfo() {
return adInfo; return adInfo;
} }
// setAdInfo方法用于设置广告详细信息属性的值外部代码可以传入一个字符串参数来更新广告详细信息的内容。
public void setAdInfo(String adInfo) { public void setAdInfo(String adInfo) {
this.adInfo = adInfo; this.adInfo = adInfo;
} }
public JSONArray getAds(){ /**
if(StringUtils.isEmpty(getAdInfo())){ * getAdsadInfoJSONArray便JSON广
* adInfoStringUtils.isEmptynull广
* 使FastJSONJSONArray.parseArrayJSONArray便
*/
public JSONArray getAds() {
if (StringUtils.isEmpty(getAdInfo())) {
return null; return null;
} }
return JSONArray.parseArray(getAdInfo()); return JSONArray.parseArray(getAdInfo());
} }
// getBusinessKey方法用于获取业务键值businessKey属性的值供外部代码获取相关的业务标识或关联信息。
public String getBusinessKey() { public String getBusinessKey() {
return businessKey; return businessKey;
} }
// setBusinessKey方法用于设置业务键值属性的值外部代码可以传入一个字符串参数来更新该属性所代表的业务相关标识信息。
public void setBusinessKey(String businessKey) { public void setBusinessKey(String businessKey) {
this.businessKey = businessKey; this.businessKey = businessKey;
} }
} }

@ -6,89 +6,141 @@ import com.baomidou.mybatisplus.activerecord.Model;
import com.baomidou.mybatisplus.annotations.TableId; import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.annotations.TableName; import com.baomidou.mybatisplus.annotations.TableName;
@TableName(value="tiku_book") // 这个类BookEntity定义了与数据库中名为“tiku_book”表对应的实体对象从命名来看它可能是用于表示书籍相关信息的数据实体类。
public class BookEntity extends Model<BookEntity>{ // 该类继承自MyBatis Plus的Model<BookEntity>类继承Model类可以方便地使用MyBatis Plus提供的一些便捷的数据库操作方法比如CRUD操作等
// 同时还实现了Serializable接口使得该类的对象能够在诸如网络传输、持久化存储等场景下进行序列化和反序列化操作保证对象状态可以被正确保存和恢复。
@TableName(value = "tiku_book")
public class BookEntity extends Model<BookEntity> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
// 使用@TableId注解标记这个字段表明它是数据库表“tiku_book”表中的主键字段在这里字段名为“uid”
// 具体的主键生成策略等相关设置如果有的话通常会根据MyBatis Plus的默认配置或者项目中额外的配置来确定
// 通过对应的getter和setter方法来操作这个属性的值外部代码可以获取或设置书籍的唯一标识符uid
@TableId @TableId
private String uid; private String uid;
// 用于存储学科的唯一标识符通过这个字段可以关联到对应的学科信息其值由外部设置并获取使用对应的getter和setter方法进行操作
// 方便在业务逻辑中确定书籍所属的学科分类等情况。
private String subjectId; private String subjectId;
// 用于存储课程的唯一标识符类似学科标识符的作用可用于关联到具体的课程信息外部代码可以通过getter和setter方法对其值进行操作
// 帮助在业务中明确书籍与具体课程之间的关联关系。
private String courseId; private String courseId;
// 用于存储书籍的名称通过getter和setter方法getName和setName外部代码能够获取或更新书籍的名称信息便于在业务中展示、查找等操作时使用。
private String name; private String name;
// 用于存储书籍的出版社信息外部代码可利用相应的getter和setter方法获取或修改该属性值在涉及书籍来源、版权等业务场景下会用到这个信息。
private String publishingHouse; private String publishingHouse;
// 用于存储书籍中包含的题目数量相关信息可能是一个数字字符串形式具体取决于业务存储格式通过对应的getter和setter方法进行操作
// 在统计、查询等业务操作中可以依据这个属性了解书籍的题目规模情况。
private String questionNum; private String questionNum;
// 用于存储书籍中所涵盖的知识点数量相关信息同样通过getter和setter方法来操作其值方便在业务逻辑中对书籍涵盖的知识点情况进行把握和处理。
private String pointNum; private String pointNum;
// 用于存储书籍的排序序号相关信息可能用于在展示、排序等场景下确定书籍的顺序通过对应的getter和setter方法进行操作
// 外部代码可以获取或修改这个序号值来调整书籍的排列顺序等情况。
private Integer orders; private Integer orders;
public String getUid() { // getName方法是遵循JavaBean规范的getter方法用于获取书籍名称属性name的值外部代码可以调用这个方法获取当前BookEntity对象所代表书籍的名称。
return uid; public String getName() {
return name;
} }
public void setUid(String uid) { // setName方法是遵循JavaBean规范的setter方法用于设置书籍名称属性的值外部代码可以传入一个字符串参数来更新当前BookEntity对象所代表书籍的名称。
this.uid = uid; public void setName(String name) {
this.name = name;
} }
// getSubjectId方法用于获取学科标识符subjectId属性的值供外部代码获取书籍所属的学科相关信息便于进行关联查询、分类统计等操作。
public String getSubjectId() { public String getSubjectId() {
return subjectId; return subjectId;
} }
// setSubjectId方法用于设置学科标识符属性的值外部代码可以传入一个字符串参数来更新书籍所属的学科信息在业务逻辑中调整书籍的学科分类归属等情况时会用到。
public void setSubjectId(String subjectId) { public void setSubjectId(String subjectId) {
this.subjectId = subjectId; this.subjectId = subjectId;
} }
// getCourseId方法用于获取课程标识符courseId属性的值方便外部代码知晓书籍与具体课程的关联情况在课程相关的业务操作中起到关联作用。
public String getCourseId() { public String getCourseId() {
return courseId; return courseId;
} }
// setCourseId方法用于设置课程标识符属性的值外部代码可以传入一个字符串参数来更新书籍对应的课程信息用于在业务中重新确定书籍与课程的关联关系。
public void setCourseId(String courseId) { public void setCourseId(String courseId) {
this.courseId = courseId; this.courseId = courseId;
} }
public String getName() { // getUid方法用于获取书籍的唯一标识符uid属性的值这个值作为主键在数据库操作以及对象标识等方面有着重要作用
return name; // 外部代码可以通过调用这个方法获取当前BookEntity对象对应的书籍在数据库中的唯一标识。
public String getUid() {
return uid;
} }
public void setName(String name) { // setUid方法用于设置书籍的唯一标识符属性的值外部代码可以传入一个字符串参数来更新这个唯一标识信息不过在实际应用中要谨慎操作
this.name = name; // 因为主键通常具有唯一性且关联着数据库中的重要数据记录,随意修改可能导致数据不一致等问题。
public void setUid(String uid) {
this.uid = uid;
} }
// getPublishingHouse方法用于获取书籍出版社publishingHouse属性的值外部代码可以通过调用这个方法获取当前BookEntity对象所代表书籍的出版社信息。
public String getPublishingHouse() { public String getPublishingHouse() {
return publishingHouse; return publishingHouse;
} }
// setPublishingHouse方法用于设置书籍出版社属性的值外部代码可以传入一个字符串参数来更新书籍的出版社信息比如在书籍信息更新、录入新书籍等场景下使用。
public void setPublishingHouse(String publishingHouse) { public void setPublishingHouse(String publishingHouse) {
this.publishingHouse = publishingHouse; this.publishingHouse = publishingHouse;
} }
// getQuestionNum方法用于获取书籍题目数量questionNum属性的值外部代码可以获取这个属性值来了解书籍包含的题目规模情况
// 在统计分析、展示等业务操作中会用到这个信息。
public String getQuestionNum() { public String getQuestionNum() {
return questionNum; return questionNum;
} }
// setQuestionNum方法用于设置书籍题目数量属性的值外部代码可以传入一个字符串参数来更新书籍所包含的题目数量信息
// 例如在题目数量发生变化(新增、删除题目等情况)时对该属性进行相应的更新操作。
public void setQuestionNum(String questionNum) { public void setQuestionNum(String questionNum) {
this.questionNum = questionNum; this.questionNum = questionNum;
} }
// getPointNum方法用于获取书籍知识点数量pointNum属性的值外部代码可以通过调用这个方法获取当前BookEntity对象所代表书籍涵盖的知识点数量情况
// 在知识点相关的业务处理、统计等场景下会用到这个属性值。
public String getPointNum() { public String getPointNum() {
return pointNum; return pointNum;
} }
// setPointNum方法用于设置书籍知识点数量属性的值外部代码可以传入一个字符串参数来更新书籍涵盖的知识点数量信息
// 比如在对书籍内容进行知识点梳理、更新后对该属性进行相应的调整操作。
public void setPointNum(String pointNum) { public void setPointNum(String pointNum) {
this.pointNum = pointNum; this.pointNum = pointNum;
} }
// getOrders方法用于获取书籍排序序号orders属性的值外部代码可以获取这个序号值来了解书籍在相关展示、排序场景下的顺序位置情况
// 并且可以通过对应的setOrders方法来修改这个序号从而调整书籍的排列顺序等。
public Integer getOrders() { public Integer getOrders() {
return orders; return orders;
} }
// setOrders方法用于设置书籍排序序号属性的值外部代码可以传入一个整数参数来更新书籍的排序序号用于在业务中改变书籍的排列顺序
// 例如在列表展示中调整书籍的先后位置等情况。
public void setOrders(Integer orders) { public void setOrders(Integer orders) {
this.orders = orders; this.orders = orders;
} }
// getSerialversionuid方法返回类的序列化版本UID这个UID在序列化和反序列化过程中用于验证类的版本一致性
// 这里直接返回了定义好的静态常量serialVersionUID的值一般不需要手动修改这个方法的实现它是按照Java序列化机制要求定义的。
public static long getSerialversionuid() { public static long getSerialversionuid() {
return serialVersionUID; return serialVersionUID;
} }
// 重写了父类Model的pkVal方法用于指定当前实体类的主键值在这里返回了getUid方法获取到的书籍唯一标识符uid
// 这样MyBatis Plus在进行一些基于主键的数据库操作如根据主键查询、更新等时就能准确知道当前实体对应的主键是什么从而正确执行相关操作。
@Override @Override
protected Serializable pkVal() { protected Serializable pkVal() {
return getUid(); return getUid();

@ -13,24 +13,49 @@ import com.tamguo.model.AdEntity;
import com.tamguo.service.IAdService; import com.tamguo.service.IAdService;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它是用于处理业务逻辑的类Spring会自动扫描并将其纳入到Spring容器管理中
// 方便在其他地方进行依赖注入等操作同时也遵循了Spring的分层架构规范使得代码结构更清晰。
@Service @Service
public class AdService extends ServiceImpl<AdMapper, AdEntity> implements IAdService{ public class AdService extends ServiceImpl<AdMapper, AdEntity> implements IAdService {
// 通过Spring的依赖注入机制自动注入 AdMapper 接口的实现类实例,
// AdMapper 应该是用于定义与广告AdEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在这个服务类中会调用它的方法来与数据库进行交互,获取广告相关的数据。
@Autowired @Autowired
AdMapper adMapper; AdMapper adMapper;
// 同样通过依赖注入机制注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存等功能,在这里主要用于缓存广告数据,减少频繁访问数据库的开销,提高系统性能。
@Autowired @Autowired
CacheService cacheService; CacheService cacheService;
/**
* findAll
* IAdService findAll
* 广
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<AdEntity> findAll() { public List<AdEntity> findAll() {
// 首先尝试从缓存中获取名为 TamguoConstant.ALL_AD 的缓存对象,这里将获取到的对象强制转换为 List<AdEntity> 类型,
// 期望从缓存中获取到之前存储的广告信息列表,如果缓存命中,就可以直接返回这个缓存中的广告列表数据,减少数据库查询操作。
List<AdEntity> adList = (List<AdEntity>) cacheService.getObject(TamguoConstant.ALL_AD); List<AdEntity> adList = (List<AdEntity>) cacheService.getObject(TamguoConstant.ALL_AD);
// 此处将 adList 赋值为 null这看起来有点奇怪可能是代码有误或者有特定的后续逻辑比如要重新获取最新数据等情况
// 正常情况下如果是想清空缓存数据重新获取,应该先删除缓存中的对应数据等操作,而不是简单赋值为 null不过先按照现有逻辑继续分析。
adList = null; adList = null;
if(adList == null || adList.isEmpty()){ // 判断从缓存中获取到的广告列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的广告信息),
// 如果满足这个条件,就需要从数据库中重新获取广告信息列表。
if (adList == null || adList.isEmpty()) {
// 通过注入的 AdMapper 调用其 selectList 方法,传入 Condition.EMPTY 参数,这表示使用默认的查询条件(通常是查询所有记录),
// 从数据库中获取所有的广告实体信息,将获取到的结果赋值给 adList 变量,用于后续操作。
adList = adMapper.selectList(Condition.EMPTY); adList = adMapper.selectList(Condition.EMPTY);
cacheService.setObject(TamguoConstant.ALL_AD, adList , 2 * 60 * 60); // 调用注入的 CacheService 的 setObject 方法将刚刚从数据库中获取到的广告列表数据adList存入缓存中
// 缓存的键名为 TamguoConstant.ALL_AD同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findAll 方法,就可以直接从缓存中获取广告数据,而不用再次查询数据库了。
cacheService.setObject(TamguoConstant.ALL_AD, adList, 2 * 60 * 60);
} }
// 最后返回获取到的广告信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况。
return adList; return adList;
} }
} }

@ -15,48 +15,91 @@ import com.tamguo.service.IAreaService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理业务逻辑相关的操作
// Spring在扫描时会自动识别并将其纳入到Spring容器中进行管理方便在其他地方进行依赖注入等操作符合Spring的分层架构设计。
@Service @Service
public class AreaService extends ServiceImpl<AreaMapper, AreaEntity> implements IAreaService{ public class AreaService extends ServiceImpl<AreaMapper, AreaEntity> implements IAreaService {
// 通过Spring的依赖注入机制自动注入 AreaMapper 接口的实现类实例,
// AreaMapper 应该是用于定义与地区AreaEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在本服务类中会调用它的方法来和数据库进行交互,获取地区相关的数据信息。
@Autowired @Autowired
private AreaMapper areaMapper; private AreaMapper areaMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在这个类中主要用于缓存地区相关的数据,以优化数据获取性能,减少数据库访问次数。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
/**
* findAll
* IAreaService findAll
* AreaMapper selectList Condition.EMPTY
* 使
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<AreaEntity> findAll() { public List<AreaEntity> findAll() {
return areaMapper.selectList(Condition.EMPTY); return areaMapper.selectList(Condition.EMPTY);
} }
@Transactional(readOnly=true) /**
* findRootArea
* IAreaService findRootArea @Transactional(readOnly = true)
*
* AreaMapper findRootArea AreaMapper
// 返回查询到的根地区信息列表。
*/
@Transactional(readOnly = true)
@Override @Override
public List<AreaEntity> findRootArea() { public List<AreaEntity> findRootArea() {
return areaMapper.findRootArea(); return areaMapper.findRootArea();
} }
@Transactional(readOnly=true) /**
* findAreaTree
* IAreaService findAreaTree
* TamguoConstant.AREA_ALL_TREE
* Result.successResult
*
*/
@Transactional(readOnly = true)
@Override @Override
public Result findAreaTree() { public Result findAreaTree() {
if(cacheService.isExist(TamguoConstant.AREA_ALL_TREE)) { if (cacheService.isExist(TamguoConstant.AREA_ALL_TREE)) {
return Result.successResult(cacheService.getObject(TamguoConstant.AREA_ALL_TREE)); return Result.successResult(cacheService.getObject(TamguoConstant.AREA_ALL_TREE));
} }
// 如果缓存中不存在相应数据,先从数据库中获取根地区信息列表,调用 AreaMapper 的 findRootArea 方法来获取,
// 这些根地区将作为地区树结构的顶层节点,后续会基于这些节点逐步构建完整的地区树。
List<AreaEntity> areaList = areaMapper.findRootArea(); List<AreaEntity> areaList = areaMapper.findRootArea();
for(AreaEntity area : areaList) {
// 遍历根地区列表,对于每个根地区,查找其下一级子地区信息,通过调用 AreaMapper 的 findByParent 方法传入当前根地区的唯一标识符area.getUid())作为参数,
// 获取该根地区对应的子地区列表,然后判断子地区列表是否为空,如果不为空,将子地区列表设置为当前根地区的子节点(通过 area.setChildren(childend) 方法设置),
// 这样就构建了地区树结构的第一层父子关系。
for (AreaEntity area : areaList) {
List<AreaEntity> childend = areaMapper.findByParent(area.getUid()); List<AreaEntity> childend = areaMapper.findByParent(area.getUid());
if(!CollectionUtils.isEmpty(childend)) { if (!CollectionUtils.isEmpty(childend)) {
area.setChildren(childend); area.setChildren(childend);
} }
for(AreaEntity a : childend) { // 对于每个子地区,再进一步查找它的下一级子地区(也就是孙子地区等更深层次的子节点),同样通过调用 AreaMapper 的 findByParent 方法,
// 传入子地区的唯一标识符a.getUid())获取其对应的子地区列表,若列表不为空,将这些子地区设置为当前子地区的子节点(通过 a.setChildren(ceList) 方法设置),
// 以此类推,通过循环嵌套的方式逐步构建出完整的地区树结构,包含多层级的父子关系。
for (AreaEntity a : childend) {
List<AreaEntity> ceList = areaMapper.findByParent(a.getUid()); List<AreaEntity> ceList = areaMapper.findByParent(a.getUid());
if(!CollectionUtils.isEmpty(ceList)) { if (!CollectionUtils.isEmpty(ceList)) {
a.setChildren(ceList); a.setChildren(ceList);
} }
} }
} }
cacheService.setObject(TamguoConstant.AREA_ALL_TREE, areaList , 60 * 60 * 2);
// 将构建好的地区树结构数据存入缓存中,缓存的键名为 TamguoConstant.AREA_ALL_TREE同时设置缓存的有效时间为 60 * 60 * 2 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findAreaTree 方法,就可以直接从缓存中获取地区树数据,而不用重新构建,提高了数据获取的性能和效率。
cacheService.setObject(TamguoConstant.AREA_ALL_TREE, areaList, 60 * 60 * 2);
// 将构建好的地区树结构数据作为成功结果返回(通过 Result.successResult 方法进行包装),方便在调用处统一处理返回结果,
// 并且符合项目中对返回结果进行统一格式封装的规范要求。
return Result.successResult(areaList); return Result.successResult(areaList);
} }
} }

@ -16,84 +16,140 @@ import com.tamguo.model.ChapterEntity;
import com.tamguo.service.IChapterService; import com.tamguo.service.IChapterService;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与章节Chapter相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循了Spring的分层架构设计规范。
@Service @Service
public class ChapterService extends ServiceImpl<ChapterMapper, ChapterEntity> implements IChapterService{ public class ChapterService extends ServiceImpl<ChapterMapper, ChapterEntity> implements IChapterService {
// 通过Spring的依赖注入机制自动注入 ChapterMapper 接口的实现类实例,
// ChapterMapper 是用于定义与章节实体ChapterEntity相关的数据库操作方法的接口例如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取章节相关的数据信息。
@Autowired @Autowired
private ChapterMapper chapterMapper; private ChapterMapper chapterMapper;
/**
* findCourseChapter
* IChapterService findCourseChapter bookId
*/
@Override @Override
public List<ChapterEntity> findCourseChapter(String bookId) { public List<ChapterEntity> findCourseChapter(String bookId) {
// 首先调用 ChapterMapper 的 findByBookId 方法,根据传入的书籍 IDbookId从数据库中获取该书籍对应的所有章节信息列表
// 这些章节信息将作为后续构建章节树形结构的基础数据。
List<ChapterEntity> chapterList = chapterMapper.findByBookId(bookId); List<ChapterEntity> chapterList = chapterMapper.findByBookId(bookId);
// 获取根chapter UID // 初始化用于存储根章节 UID 的变量,初始值设为空字符串,后续会在章节列表中查找真正的根章节 UID 并赋值。
String rootUid = StringUtils.EMPTY; String rootUid = StringUtils.EMPTY;
for(int i=0 ; i<chapterList.size() ; i++){ // 遍历获取到的章节列表,查找父 ID 等于 TamguoConstant.CHAPTER_DEFAULT_ROOT_UID这应该是预定义的表示根章节的唯一标识符常量的章节
// 一旦找到,就将该章节的 UID 赋值给 rootUid 变量,确定根章节的唯一标识。
for (int i = 0; i < chapterList.size(); i++) {
ChapterEntity chapter = chapterList.get(i); ChapterEntity chapter = chapterList.get(i);
if(chapter.getParentId().equals(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID)){ if (chapter.getParentId().equals(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID)) {
rootUid = chapter.getUid(); rootUid = chapter.getUid();
} }
} }
// 获取第一层结构
// 创建一个新的 ArrayList用于存储第一层章节结构即根章节下直接关联的子章节后续会将符合条件的章节添加到这个列表中。
List<ChapterEntity> entitys = new ArrayList<>(); List<ChapterEntity> entitys = new ArrayList<>();
for(int i=0 ; i<chapterList.size() ; i++){ // 再次遍历章节列表,查找父 ID 等于刚才确定的根章节 UIDrootUid的章节将这些章节添加到 entitys 列表中,
// 这样就构建出了章节树形结构的第一层,即根章节下的直接子章节集合。
for (int i = 0; i < chapterList.size(); i++) {
ChapterEntity chapter = chapterList.get(i); ChapterEntity chapter = chapterList.get(i);
if(rootUid.equals(chapter.getParentId())){ if (rootUid.equals(chapter.getParentId())) {
entitys.add(chapter); entitys.add(chapter);
} }
} }
for(int i=0 ; i<entitys.size() ; i++){
// 遍历第一层的章节列表entitys对于每个章节entity创建一个新的 ArrayList 用于存储它的子章节,
// 然后再次遍历章节总列表chapterList查找父 ID 等于当前章节 UIDentity.getUid()的章节将找到的章节添加到子章节列表childs
// 最后通过 entity.setChildChapterList(childs) 方法将子章节列表设置为当前章节的子节点,从而构建出了第二层章节结构,即根章节的子章节下的子章节。
for (int i = 0; i < entitys.size(); i++) {
ChapterEntity entity = entitys.get(i); ChapterEntity entity = entitys.get(i);
List<ChapterEntity> childs = new ArrayList<>(); List<ChapterEntity> childs = new ArrayList<>();
for(int k=0 ; k<chapterList.size() ; k++){ for (int k = 0; k < chapterList.size(); k++) {
ChapterEntity chapter = chapterList.get(k); ChapterEntity chapter = chapterList.get(k);
if(entity.getUid().equals(chapter.getParentId())){ if (entity.getUid().equals(chapter.getParentId())) {
childs.add(chapter); childs.add(chapter);
} }
} }
entity.setChildChapterList(childs); entity.setChildChapterList(childs);
} }
for(int i=0 ; i<entitys.size() ; i++){
// 接着对于第一层章节列表entitys中的每个章节及其子章节列表childs进一步遍历子章节列表
// 对于每个子章节child再创建一个新的 ArrayList 用于存储它的下一层子章节tmpChilds
// 然后再次遍历章节总列表chapterList查找父 ID 等于当前子章节 UIDchild.getUid()的章节将找到的章节添加到下一层子章节列表tmpChilds
// 最后通过 child.setChildChapterList(tmpChilds) 方法将下一层子章节列表设置为当前子章节的子节点,以此类推,构建出更完整的多层级章节树形结构。
for (int i = 0; i < entitys.size(); i++) {
List<ChapterEntity> childs = entitys.get(i).getChildChapterList(); List<ChapterEntity> childs = entitys.get(i).getChildChapterList();
for(int k=0 ; k<childs.size() ; k++){ for (int k = 0; k < childs.size(); k++) {
ChapterEntity child = childs.get(k); ChapterEntity child = childs.get(k);
List<ChapterEntity> tmpChilds = new ArrayList<>(); List<ChapterEntity> tmpChilds = new ArrayList<>();
for(int n=0 ; n<chapterList.size() ; n++){ for (int n = 0; n < chapterList.size(); n++) {
ChapterEntity chapter = chapterList.get(n); ChapterEntity chapter = chapterList.get(n);
if(child.getUid().equals(chapter.getParentId())){ if (child.getUid().equals(chapter.getParentId())) {
tmpChilds.add(chapter); tmpChilds.add(chapter);
} }
} }
child.setChildChapterList(tmpChilds); child.setChildChapterList(tmpChilds);
} }
} }
// 最后返回构建好的章节树形结构数据,即包含了多层级父子关系的章节列表,这个列表以根章节及其子章节、孙章节等形式呈现,方便在业务中进行展示、操作等处理。
return entitys; return entitys;
} }
/**
* findById
* IChapterService findById uid
* ChapterMapper selectById UID uid ChapterMapper
*/
@Override @Override
public ChapterEntity findById(String uid) { public ChapterEntity findById(String uid) {
return chapterMapper.selectById(uid); return chapterMapper.selectById(uid);
} }
/**
* findNextPoint
* IChapterService findNextPoint uidorders
// 调用了 ChapterMapper 的 findNextPoint 方法,传入章节 UID 和顺序号参数,由 ChapterMapper 负责在数据库中查找并返回符合条件的章节实体对象。
*/
@Override @Override
public ChapterEntity findNextPoint(String uid , Integer orders) { public ChapterEntity findNextPoint(String uid, Integer orders) {
return chapterMapper.findNextPoint(uid , orders); return chapterMapper.findNextPoint(uid, orders);
} }
@Transactional(readOnly=false) /**
* getChapterTree
* IChapterService getChapterTree IDcourseId
* @Transactional(readOnly = false)
*/
@Transactional(readOnly = false)
@Override @Override
public List<ChapterEntity> getChapterTree(String courseId) { public List<ChapterEntity> getChapterTree(String courseId) {
if(StringUtils.isEmpty(courseId) || "null".equals(courseId)){ // 首先判断传入的课程 ID 是否为空字符串或者等于 "null"(这里判断 "null" 的方式不太严谨,更合适的做法可能是判断是否为 null 值,不过先按照现有逻辑分析),
// 如果满足这个条件,说明没有有效的课程 ID 传入,那么调用 rootChapterNode 方法返回一个默认的根章节节点列表,作为章节树的基础结构(可能用于没有具体课程关联时的默认展示等情况)。
if (StringUtils.isEmpty(courseId) || "null".equals(courseId)) {
return rootChapterNode(); return rootChapterNode();
} }
// 根据传入的课程 ID调用 ChapterMapper 的 findByBookId 方法(这里可能命名不太准确,根据上下文推测应该是根据课程相关的标识去查找对应的章节信息,也许后续需要确认方法名是否合适),
// 从数据库中获取该课程对应的章节信息列表,然后判断获取到的列表是否为空(即没有找到对应的章节信息),
// 如果为空,同样调用 rootChapterNode 方法返回默认的根章节节点列表作为章节树结构。
List<ChapterEntity> list = chapterMapper.findByBookId(courseId); List<ChapterEntity> list = chapterMapper.findByBookId(courseId);
if(CollectionUtils.isEmpty(list)) { if (CollectionUtils.isEmpty(list)) {
return rootChapterNode(); return rootChapterNode();
} }
// 如果获取到了有效的章节信息列表,就直接返回这个列表,该列表后续可用于构建具体的章节树形结构或者直接在业务中进行展示、处理等操作,具体取决于业务逻辑要求。
return list; return list;
} }
private List<ChapterEntity> rootChapterNode(){ /**
* rootChapterNode
* ChapterEntity
* ID UID ID
* 使
*/
private List<ChapterEntity> rootChapterNode() {
ChapterEntity chapter = new ChapterEntity(); ChapterEntity chapter = new ChapterEntity();
chapter.setCourseId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID); chapter.setCourseId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID);
chapter.setOrders(0); chapter.setOrders(0);
@ -104,5 +160,4 @@ public class ChapterService extends ServiceImpl<ChapterMapper, ChapterEntity> im
chapter.setParentId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID); chapter.setParentId(TamguoConstant.CHAPTER_DEFAULT_ROOT_UID);
return Arrays.asList(chapter); return Arrays.asList(chapter);
} }
} }

@ -18,80 +18,148 @@ import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils; import com.tamguo.util.ShiroUtils;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与会员Member相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service @Service
public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> implements IMemberService{ public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> implements IMemberService {
// 通过Spring的依赖注入机制自动注入 MemberMapper 接口的实现类实例,
// MemberMapper 是用于定义与会员实体MemberEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取会员相关的数据信息。
@Autowired @Autowired
private MemberMapper memberMapper; private MemberMapper memberMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在会员相关业务中用于缓存如登录失败次数、验证码等信息,以优化业务操作性能。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
/**
* login
* IMemberService login
*/
@Override @Override
public Result login(String username, String password) { public Result login(String username, String password) {
// 首先通过 MemberMapper 的 findByUsername 方法根据传入的用户名username从数据库中查找对应的会员实体信息
// 如果没有找到(即返回 null说明用户名不存在直接返回相应的结果提示用户名或密码有误封装在 Result 对象中返回给调用者。
MemberEntity member = memberMapper.findByUsername(username); MemberEntity member = memberMapper.findByUsername(username);
if(member == null){ if (member == null) {
return Result.result(201, member, "用户名或密码有误,请重新输入或找回密码"); return Result.result(201, member, "用户名或密码有误,请重新输入或找回密码");
} }
// 获取当前会员的登录失败次数,调用本类中的 getLoginFailureCount 方法来获取,后续用于判断是否达到限制次数等情况。
Integer loginFailureCount = this.getLoginFailureCount(member); Integer loginFailureCount = this.getLoginFailureCount(member);
if(!new Sha256Hash(password).toHex().equals(member.getPassword())){
// 使用 Sha256Hash 对传入的密码进行哈希处理并与数据库中存储的会员密码member.getPassword())进行比对,
// 如果不相等,说明密码错误,此时将登录失败次数加 1并调用 updateLoginFailureCount 方法更新缓存中的登录失败次数记录,
// 然后返回相应的错误提示结果(用户名或密码有误),封装在 Result 对象中。
if (!new Sha256Hash(password).toHex().equals(member.getPassword())) {
loginFailureCount++; loginFailureCount++;
this.updateLoginFailureCount(member , loginFailureCount); this.updateLoginFailureCount(member, loginFailureCount);
return Result.result(202, member, "用户名或密码有误,请重新输入或找回密码"); return Result.result(202, member, "用户名或密码有误,请重新输入或找回密码");
} }
this.updateLoginFailureCount(member , 0);
// 如果密码正确,将登录失败次数重置为 0表示此次登录成功清除之前的失败记录调用 updateLoginFailureCount 方法更新缓存中的登录失败次数,
// 最后返回登录成功的结果提示,封装在 Result 对象中返回给调用者,表示会员登录成功。
this.updateLoginFailureCount(member, 0);
return Result.result(200, member, "登录成功"); return Result.result(200, member, "登录成功");
} }
public void updateLoginFailureCount(MemberEntity member , Integer loginFailureCount){ /**
cacheService.setObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid(), loginFailureCount , 2 * 60 * 60); * updateLoginFailureCount
* TamguoConstant.LOGIN_FAILURE_COUNT member.getUid()
* loginFailureCount 2 * 60 * 60 2
// 这样在这 2 小时内,后续获取登录失败次数时会从缓存中读取该值,避免频繁访问数据库来获取和更新这个数据,提高业务操作效率。
*/
public void updateLoginFailureCount(MemberEntity member, Integer loginFailureCount) {
cacheService.setObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid(), loginFailureCount, 2 * 60 * 60);
} }
public Integer getLoginFailureCount(MemberEntity member){ /**
if(member == null){ * getLoginFailureCount
* null null 0
* TamguoConstant.LOGIN_FAILURE_COUNT member.getUid()
// 如果不存在,同样返回 0表示没有记录到该会员的登录失败次数如果存在则从缓存中获取对应的登录失败次数类型转换为 Integer并返回。
*/
public Integer getLoginFailureCount(MemberEntity member) {
if (member == null) {
return 0; return 0;
} }
if(!cacheService.isExist(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid())){ if (!cacheService.isExist(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid())) {
return 0; return 0;
} }
return (Integer)cacheService.getObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid()); return (Integer) cacheService.getObject(TamguoConstant.LOGIN_FAILURE_COUNT + member.getUid());
} }
/**
* checkUsername
* IMemberService checkUsername 使
// 通过 MemberMapper 的 findByUsername 方法根据传入的用户名username从数据库中查找对应的会员实体
// 如果找到(即返回的会员实体不为 null说明用户名已经存在返回相应的提示结果该用户名已经存在封装在 Result 对象中;
// 如果没有找到,则返回表示用户名可用的提示结果,同样封装在 Result 对象中返回给调用者。
*/
@Override @Override
public Result checkUsername(String username) { public Result checkUsername(String username) {
MemberEntity member = memberMapper.findByUsername(username); MemberEntity member = memberMapper.findByUsername(username);
if(member != null){ if (member!= null) {
return Result.result(201, null, "该用户名已经存在"); return Result.result(201, null, "该用户名已经存在");
} }
return Result.result(200, null, "该用户名可用"); return Result.result(200, null, "该用户名可用");
} }
/**
* checkMobile
* IMemberService checkMobile 使
// 通过 MemberMapper 的 findByMobile 方法根据传入的手机号mobile从数据库中查找对应的会员实体
// 如果找到(即返回的会员实体不为 null说明手机号已经存在返回相应的提示结果该手机号已经存在封装在 Result 对象中;
// 如果没有找到,则返回表示手机号可用的提示结果,同样封装在 Result 对象中返回给调用者。
*/
@Override @Override
public Result checkMobile(String mobile) { public Result checkMobile(String mobile) {
MemberEntity member = memberMapper.findByMobile(mobile); MemberEntity member = memberMapper.findByMobile(mobile);
if(member != null){ if (member!= null) {
return Result.result(201, null, "该手机号已经存在"); return Result.result(201, null, "该手机号已经存在");
} }
return Result.result(200, null, "该手机号可用"); return Result.result(200, null, "该手机号可用");
} }
@Transactional(readOnly=false) /**
* register
* IMemberService register
* @Transactional(readOnly = false)
*/
@Transactional(readOnly = false)
@Override @Override
public Result register(MemberEntity member) { public Result register(MemberEntity member) {
// 首先通过 MemberMapper 的 findByUsername 方法根据传入的要注册的用户名member.getUsername())从数据库中查找是否已存在该用户名的会员,
// 如果找到(即返回的会员实体不为 null说明用户名已被使用返回相应的提示结果该用户已经存在封装在 Result 对象中。
MemberEntity m = memberMapper.findByUsername(member.getUsername()); MemberEntity m = memberMapper.findByUsername(member.getUsername());
if(m != null){ if (m!= null) {
return Result.result(201, null, "该用户已经存在"); return Result.result(201, null, "该用户已经存在");
} }
// 接着通过 MemberMapper 的 findByMobile 方法根据传入的要注册的手机号member.getMobile())从数据库中查找是否已存在该手机号的会员,
// 如果找到(即返回的会员实体不为 null说明手机号已被使用返回相应的提示结果该手机号已经存在封装在 Result 对象中。
m = memberMapper.findByMobile(member.getMobile()); m = memberMapper.findByMobile(member.getMobile());
if(m != null){ if (m!= null) {
return Result.result(202, null, "该手机号已经存在"); return Result.result(202, null, "该手机号已经存在");
} }
if(!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())){
// 检查缓存中是否存在以 TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX 和要注册的手机号member.getMobile())拼接而成的键对应的验证码缓存数据,
// 如果不存在,说明验证码错误(可能未发送或者已过期等情况),返回相应的提示结果(验证码错误),封装在 Result 对象中。
if (!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())) {
return Result.result(203, null, "验证码错误"); return Result.result(203, null, "验证码错误");
} }
// 从缓存中获取对应的验证码将获取到的验证码与传入的要注册的会员实体中的验证码member.getVerifyCode())进行比对,
// 如果不相等,说明验证码错误,返回相应的提示结果(验证码错误),封装在 Result 对象中。
String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile()); String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile());
if(!code.equals(member.getVerifyCode())){ if (!code.equals(member.getVerifyCode())) {
return Result.result(204, null, "验证码错误"); return Result.result(204, null, "验证码错误");
} }
// 创建一个新的 MemberEntity 实例,用于存储要注册的会员信息,设置了一些默认值(如头像使用默认头像 TamguoConstant.DEFAULT_MEMBER_AVATAR
// 并将传入的会员实体中的相关信息(如手机号、密码、用户名等)设置到新实例中,其中密码使用 Sha256Hash 进行哈希处理后存储,提高安全性。
MemberEntity entity = new MemberEntity(); MemberEntity entity = new MemberEntity();
entity.setAvatar(TamguoConstant.DEFAULT_MEMBER_AVATAR); entity.setAvatar(TamguoConstant.DEFAULT_MEMBER_AVATAR);
entity.setMobile(member.getMobile()); entity.setMobile(member.getMobile());
@ -101,30 +169,51 @@ public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> imple
entity.setSubjectId(member.getSubjectId()); entity.setSubjectId(member.getSubjectId());
entity.setCourseId(member.getCourseId()); entity.setCourseId(member.getCourseId());
entity.setEmail(member.getEmail()); entity.setEmail(member.getEmail());
// 通过 MemberMapper 的 insert 方法将新的会员实体信息插入到数据库中,完成会员注册操作,
// 最后返回注册成功的提示结果,封装在 Result 对象中返回给调用者,表示会员注册成功。
memberMapper.insert(entity); memberMapper.insert(entity);
return Result.result(200, entity, "注册成功"); return Result.result(200, entity, "注册成功");
} }
/**
* checkAccount
* IMemberService checkAccount account
// 首先判断传入的账号是否为空字符串,如果为空,说明账号不存在,返回相应的提示结果(帐号不存在!),封装在 Result 对象中。
// 接着通过 MemberMapper 的 findByUsernameOrEmailOrMobile 方法根据传入的账号从数据库中查找对应的会员实体,
// 如果没有找到(即返回的会员实体为 null同样返回表示账号不存在的提示结果封装在 Result 对象中;
// 如果找到,则返回表示该账号存在的提示结果,封装在 Result 对象中返回给调用者。
*/
@Override @Override
public Result checkAccount(String account) { public Result checkAccount(String account) {
if(StringUtils.isEmpty(account)){ if (StringUtils.isEmpty(account)) {
return Result.result(201, null, "帐号不存在!"); return Result.result(201, null, "帐号不存在!");
} }
MemberEntity member = memberMapper.findByUsernameOrEmailOrMobile(account); MemberEntity member = memberMapper.findByUsernameOrEmailOrMobile(account);
if(member == null){ if (member == null) {
return Result.result(201, null, "帐号不存在!"); return Result.result(201, null, "帐号不存在!");
} }
return Result.result(200, null, "该帐号存在"); return Result.result(200, null, "该帐号存在");
} }
/**
* confirmAccount
* IMemberService confirmAccount
// 首先判断传入的账号是否为空字符串,如果为空,说明账号不存在,返回相应的提示结果(帐号不存在!),封装在 Result 对象中。
// 接着通过 MemberMapper 的 findByUsernameOrEmailOrMobile 方法根据传入的账号从数据库中查找对应的会员实体,
// 如果没有找到(即返回的会员实体为 null同样返回表示账号不存在的提示结果封装在 Result 对象中。
// 然后通过 ShiroUtils 的 getKaptcha 方法获取验证码(根据 Constants.KAPTCHA_SESSION_KEY 这个键从相应的会话等地方获取),
// 将获取到的验证码与传入的验证验证码veritycode进行比对如果不相等说明验证码错误返回相应的提示结果验证码错误封装在 Result 对象中。
// 如果验证码正确,则返回表示该账号存在的提示结果,封装在 Result 对象中返回给调用者。
*/
@Override @Override
public Result confirmAccount(String account, String veritycode) { public Result confirmAccount(String account, String veritycode) {
if(StringUtils.isEmpty(account)){ if (StringUtils.isEmpty(account)) {
return Result.result(201, null, "帐号不存在!"); return Result.result(201, null, "帐号不存在!");
} }
MemberEntity member = memberMapper.findByUsernameOrEmailOrMobile(account); MemberEntity member = memberMapper.findByUsernameOrEmailOrMobile(account);
if(member == null){ if (member == null) {
return Result.result(201, null, "帐号不存在!"); return Result.result(201, null, "帐号不存在!");;
} }
String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY); String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);
if (!veritycode.equalsIgnoreCase(kaptcha)) { if (!veritycode.equalsIgnoreCase(kaptcha)) {
@ -133,96 +222,37 @@ public class MemberService extends ServiceImpl<MemberMapper, MemberEntity> imple
return Result.result(200, member, "该帐号存在"); return Result.result(200, member, "该帐号存在");
} }
/**
* securityCheck
* IMemberService securityCheck isEmail
// 首先通过 MemberMapper 的 findByUsername 方法根据传入的用户名username从数据库中查找对应的会员实体
// 如果 isEmail 参数为 "1",表示验证邮箱验证码,此时检查缓存中是否存在以 TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX 和会员的邮箱member.getEmail())拼接而成的键对应的验证码缓存数据,
// 如果不存在,说明验证码错误,返回相应的提示结果(验证码错误),封装在 Result 对象中。
// 接着从缓存中获取对应的验证码将获取到的验证码与传入的验证验证码vcode进行比对如果不相等说明验证码错误返回相应的提示结果验证码错误封装在 Result 对象中。
// 如果 isEmail 参数不为 "1",表示验证手机号验证码,进行类似的操作,检查手机号对应的验证码缓存数据是否存在以及比对验证码是否正确,
// 如果验证通过,生成一个唯一的随机字符串(使用 UUID.randomUUID().toString())作为键,将用户名存入缓存中(缓存键名为 TamguoConstant.SECURITY_CHECK_PREFIX 加上生成的随机字符串),
// 设置缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),最后返回安全验证通过的提示结果,封装在 Result 对象中返回给调用者,同时将生成的随机字符串作为结果数据返回。
*/
@Override @Override
public Result securityCheck(String username , String isEmail , String vcode) { public Result securityCheck(String username, String isEmail, String vcode) {
MemberEntity member = memberMapper.findByUsername(username); MemberEntity member = memberMapper.findByUsername(username);
if("1".equals(isEmail)){ if ("1".equals(isEmail)) {
if(!cacheService.isExist(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail())){ if (!cacheService.isExist(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail())) {
return Result.result(201, member, "验证码错误"); return Result.result(201, member, "验证码错误");
} }
String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail()); String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MAIL_FIND_PASSWORD_PREFIX + member.getEmail());
if(!code.equals(vcode)){ if (!code.equals(vcode)) {
return Result.result(202, member, "验证码错误"); return Result.result(202, member, "验证码错误");
} }
}else{ } else {
if(!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())){ if (!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())) {
return Result.result(203, member, "验证码错误"); return Result.result(203, member, "验证码错误");
} }
String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile()); String code = (String) cacheService.getObject(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile());
if(!code.equals(vcode)){ if (!code.equals(vcode)) {
return Result.result(204, member, "验证码错误"); return Result.result(204, member, "验证码错误");
} }
} }
String key = UUID.randomUUID().toString(); String key = UUID.randomUUID().toString();
cacheService.setObject(TamguoConstant.SECURITY_CHECK_PREFIX + key, username , 2 * 60 * 60); cacheService.setObject(TamguoConstant.SECURITY_CHECK_PREFIX + key, username, 2 * 60 * 60);
return Result.result(200, key, "安全验证通过"); return Result.result(200
}
@Override
public Result resetPassword(String resetPasswordKey , String username , String password, String verifypwd) {
if(cacheService.isExist(TamguoConstant.SECURITY_CHECK_PREFIX + resetPasswordKey)){
MemberEntity member = memberMapper.findByUsername(username);
if(password.equals(verifypwd)){
member.setPassword(new Sha256Hash(password).toHex());
memberMapper.updateById(member);
}
}
return Result.result(200, null, "更新成功");
}
@Transactional(readOnly=false)
@Override
public void updateMember(MemberEntity member) {
MemberEntity entity = memberMapper.selectById(ShiroUtils.getUserId());
entity.setAvatar(member.getAvatar());
entity.setEmail(member.getEmail());
entity.setMobile(member.getMobile());
entity.setCourseId(member.getCourseId());
entity.setSubjectId(member.getSubjectId());
entity.setNickName(member.getNickName());
memberMapper.updateById(entity);
}
@Transactional(readOnly=true)
@Override
public MemberEntity findByUid(String uid) {
return memberMapper.selectById(uid);
}
@Transactional(readOnly=true)
@Override
public MemberEntity findByUsername(String username) {
return memberMapper.findByUsername(username);
}
@Transactional(readOnly=false)
@Override
public void updateLastLoginTime(String uid) {
MemberEntity member = memberMapper.selectById(uid);
member.setLastLoginTime(DateUtil.getTime());
memberMapper.updateById(member);
}
@Override
public MemberEntity findCurrMember() {
MemberEntity member = memberMapper.selectById(ShiroUtils.getUserId());
member.setPassword(null);
return member;
}
@Transactional(readOnly=false)
@Override
public Result updatePwd(MemberEntity member) {
MemberEntity entity = memberMapper.selectById(ShiroUtils.getUserId());
if(!entity.getPassword().equals(new Sha256Hash(member.getPassword()).toHex())) {
return Result.result(501, null, "旧密码错误!");
}
if(!cacheService.isExist(TamguoConstant.ALIYUN_MOBILE_SMS_PREFIX + member.getMobile())){
return Result.result(502, null, "验证码错误");
}
entity.setPassword(new Sha256Hash(member.getNowPassword()).toHex());
return Result.result(0, null, "修改成功");
}
}

@ -12,101 +12,188 @@ import com.tamguo.model.MenuEntity;
import com.tamguo.service.IMenuService; import com.tamguo.service.IMenuService;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理与菜单Menu相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循了Spring的分层架构设计规范。
@Service @Service
public class MenuService extends ServiceImpl<MenuMapper, MenuEntity> implements IMenuService{ public class MenuService extends ServiceImpl<MenuMapper, MenuEntity> implements IMenuService {
// 通过Spring的依赖注入机制自动注入 MenuMapper 接口的实现类实例,
// MenuMapper 是用于定义与菜单实体MenuEntity相关的数据库操作方法的接口例如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取菜单相关的数据信息。
@Autowired @Autowired
private MenuMapper menuMapper; private MenuMapper menuMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在这个类中主要用于缓存不同类型的菜单数据,
// 以减少频繁访问数据库获取菜单信息的开销,提高系统性能,优化菜单相关业务操作的响应速度。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
/**
* findMenus
* IMenuService findMenus
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<MenuEntity> findMenus() { public List<MenuEntity> findMenus() {
// 首先尝试从缓存中获取名为 TamguoConstant.INDEX_MENU 的缓存对象,并将其强制转换为 List<MenuEntity> 类型,
// 期望从缓存中获取到之前存储的首页菜单信息列表,如果缓存命中,就可以直接返回这个缓存中的菜单列表数据,避免重复查询数据库。
List<MenuEntity> menuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.INDEX_MENU)); List<MenuEntity> menuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.INDEX_MENU));
// 判断从缓存中获取到的菜单列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的菜单信息),
// 如果满足这个条件,就需要从数据库中重新获取首页菜单信息并构建菜单结构,然后存入缓存中供后续使用。
if (menuList == null || menuList.isEmpty()) { if (menuList == null || menuList.isEmpty()) {
// 通过注入的 MenuMapper 调用其 findFatherMenus 方法,获取首页的父级菜单信息列表,这些父级菜单将作为构建菜单树形结构的顶层节点,
// 后续会基于这些父菜单查找并添加对应的子菜单,形成完整的菜单结构。
menuList = menuMapper.findFatherMenus(); menuList = menuMapper.findFatherMenus();
for(MenuEntity menu : menuList){ // 遍历获取到的父级菜单列表对于每个父菜单menu通过调用 MenuMapper 的 findMenuByParentId 方法传入当前父菜单的唯一标识符menu.getUid())作为参数,
// 获取该父菜单对应的子菜单列表,然后将获取到的子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),
// 这样就构建了首页菜单的树形结构,包含了父子菜单的层级关系。
for (MenuEntity menu : menuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid()); List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects); menu.setChildSubjects(childSubjects);
} }
cacheService.setObject(TamguoConstant.INDEX_MENU, menuList , 2 * 60 * 60); // 将构建好的首页菜单树形结构数据存入缓存中,缓存的键名为 TamguoConstant.INDEX_MENU同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findMenus 方法,就可以直接从缓存中获取首页菜单数据,而不用再次从数据库中查询和构建菜单结构了,提高了数据获取的效率。
cacheService.setObject(TamguoConstant.INDEX_MENU, menuList, 2 * 60 * 60);
} }
// 最后返回获取到的首页菜单信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况。
return menuList; return menuList;
} }
/**
* findAllMenus
* IMenuService findAllMenus
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<MenuEntity> findAllMenus() { public List<MenuEntity> findAllMenus() {
// 首先尝试从缓存中获取名为 TamguoConstant.ALL_INDEX_MENU 的缓存对象,并将其强制转换为 List<MenuEntity> 类型,
// 期望获取到之前存储的所有菜单信息列表,如果缓存命中,直接返回该缓存中的菜单列表数据即可。
List<MenuEntity> allMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.ALL_INDEX_MENU)); List<MenuEntity> allMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.ALL_INDEX_MENU));
if(allMenuList == null || allMenuList.isEmpty()){ // 判断从缓存中获取到的所有菜单列表是否为 null 或者为空列表,如果是,就需要从数据库中重新获取并构建所有菜单的树形结构数据,然后存入缓存。
if (allMenuList == null || allMenuList.isEmpty()) {
// 通过 MenuMapper 的 findAllFatherMenus 方法从数据库中获取所有的父级菜单信息列表,这些父菜单将作为构建整个菜单树形结构的顶层节点基础。
allMenuList = menuMapper.findAllFatherMenus(); allMenuList = menuMapper.findAllFatherMenus();
for(MenuEntity menu : allMenuList){ // 遍历所有的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取其对应的子菜单列表,
// 然后将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出包含所有菜单的树形结构,展示完整的菜单层级关系。
for (MenuEntity menu : allMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid()); List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects); menu.setChildSubjects(childSubjects);
} }
cacheService.setObject(TamguoConstant.ALL_INDEX_MENU, allMenuList , 2 * 60 * 60); // 将构建好的所有菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.ALL_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 以便后续在缓存有效期内可以直接从缓存获取所有菜单数据,减少数据库查询操作,提高性能。
cacheService.setObject(TamguoConstant.ALL_INDEX_MENU, allMenuList, 2 * 60 * 60);
} }
// 最后返回获取到的所有菜单信息列表,该列表可能来自缓存或者是新从数据库查询构建后存入缓存再返回的,具体取决于缓存情况和之前的逻辑执行结果。
return allMenuList; return allMenuList;
} }
/**
* findLeftMenus
* IMenuService findLeftMenus
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<MenuEntity> findLeftMenus() { public List<MenuEntity> findLeftMenus() {
// 先从缓存中获取名为 TamguoConstant.LEFT_INDEX_MENU 的缓存对象,并强制转换为 List<MenuEntity> 类型,尝试获取之前缓存的左侧菜单信息列表,
// 如果缓存中有数据,就直接返回该缓存中的菜单列表数据,避免再次查询数据库。
List<MenuEntity> leftMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.LEFT_INDEX_MENU)); List<MenuEntity> leftMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.LEFT_INDEX_MENU));
if(leftMenuList == null || leftMenuList.isEmpty()){ // 判断从缓存中获取到的左侧菜单列表是否为 null 或者为空,如果是,就需要从数据库中重新获取并构建左侧菜单的树形结构数据,然后存入缓存供后续使用。
if (leftMenuList == null || leftMenuList.isEmpty()) {
// 通过 MenuMapper 的 findLeftFatherMenus 方法从数据库中获取左侧菜单的父级菜单信息列表,这些父菜单将作为左侧菜单树形结构的顶层节点。
leftMenuList = menuMapper.findLeftFatherMenus(); leftMenuList = menuMapper.findLeftFatherMenus();
for(MenuEntity menu : leftMenuList){ // 遍历左侧菜单的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取对应的子菜单列表,
// 再将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出左侧菜单的树形结构,包含父子菜单的层级关系。
for (MenuEntity menu : leftMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid()); List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects); menu.setChildSubjects(childSubjects);
} }
cacheService.setObject(TamguoConstant.LEFT_INDEX_MENU, leftMenuList , 2 * 60 * 60); // 将构建好的左侧菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.LEFT_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样后续在有效期内再次调用 findLeftMenus 方法时,就可以直接从缓存获取左侧菜单数据,提高获取数据的效率。
cacheService.setObject(TamguoConstant.LEFT_INDEX_MENU, leftMenuList, 2 * 60 * 60);
} }
// 最后返回获取到的左侧菜单信息列表,该列表的来源取决于缓存是否命中以及之前的逻辑执行情况,可能是缓存数据也可能是新从数据库获取后存入缓存再返回的数据。
return leftMenuList; return leftMenuList;
} }
/**
* findChapterMenus
* IMenuService findChapterMenus
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<MenuEntity> findChapterMenus() { public List<MenuEntity> findChapterMenus() {
// 首先从缓存中获取名为 TamguoConstant.CHAPTER_INDEX_MENU 的缓存对象,并强制转换为 List<MenuEntity> 类型,尝试获取之前缓存的章节相关菜单信息列表,
// 如果缓存中有有效数据,就直接返回该缓存中的菜单列表数据,无需再次查询数据库。
List<MenuEntity> chapterMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.CHAPTER_INDEX_MENU)); List<MenuEntity> chapterMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.CHAPTER_INDEX_MENU));
if(chapterMenuList == null || chapterMenuList.isEmpty()){ // 判断从缓存中获取到的章节菜单列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取并构建章节菜单的树形结构数据,然后存入缓存以便后续使用。
if (chapterMenuList == null || chapterMenuList.isEmpty()) {
// 通过 MenuMapper 的 findChapterFatherMenus 方法从数据库中获取章节相关菜单的父级菜单信息列表,这些父菜单将作为章节菜单树形结构的顶层节点。
chapterMenuList = menuMapper.findChapterFatherMenus(); chapterMenuList = menuMapper.findChapterFatherMenus();
for(MenuEntity menu : chapterMenuList){ // 遍历章节菜单的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取对应的子菜单列表,
// 接着将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出章节菜单的树形结构,体现父子菜单的层级关系。
for (MenuEntity menu : chapterMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid()); List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects); menu.setChildSubjects(childSubjects);
} }
cacheService.setObject(TamguoConstant.CHAPTER_INDEX_MENU, chapterMenuList , 2 * 60 * 60); // 将构建好的章节菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.CHAPTER_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样在后续的 2 小时内,再次调用 findChapterMenus 方法时,就可以直接从缓存获取章节菜单数据,提高数据获取效率,减少数据库操作开销。
cacheService.setObject(TamguoConstant.CHAPTER_INDEX_MENU, chapterMenuList, 2 * 60 * 60);
} }
// 最后返回获取到的章节菜单信息列表,其来源取决于缓存是否命中以及之前的逻辑执行情况,可能是直接从缓存获取的数据,也可能是新从数据库获取后存入缓存再返回的数据。
return chapterMenuList; return chapterMenuList;
} }
/**
* findFooterMenus
* IMenuService findFooterMenus
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<MenuEntity> findFooterMenus() { public List<MenuEntity> findFooterMenus() {
// 先从缓存中获取名为 TamguoConstant.FOOTER_INDEX_MENU 的缓存对象,并强制转换为 List<MenuEntity> 类型,尝试获取之前缓存的底部菜单信息列表,
// 这里有个特殊处理,先将获取到的列表赋值为 null不过从常规逻辑来看这样可能不太合适一般如果要清空缓存重新获取数据会有更合理的删除缓存等操作方式
// 暂时按照现有代码逻辑继续分析,后续可能需要确认此处的真实意图是否合理。
List<MenuEntity> footerMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.FOOTER_INDEX_MENU)); List<MenuEntity> footerMenuList = ((List<MenuEntity>) cacheService.getObject(TamguoConstant.FOOTER_INDEX_MENU));
footerMenuList = null; footerMenuList = null;
if(footerMenuList == null || footerMenuList.isEmpty()){ // 判断底部菜单列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取并构建底部菜单的树形结构数据,然后存入缓存供后续调用使用。
if (footerMenuList == null || footerMenuList.isEmpty()) {
// 通过 MenuMapper 的 findFooterFatherMenus 方法从数据库中获取底部菜单的父级菜单信息列表,这些父菜单将作为底部菜单树形结构的顶层节点。
footerMenuList = menuMapper.findFooterFatherMenus(); footerMenuList = menuMapper.findFooterFatherMenus();
for(MenuEntity menu : footerMenuList){ // 遍历底部菜单的父级菜单列表对于每个父菜单menu调用 MenuMapper 的 findMenuByParentId 方法传入父菜单的唯一标识符menu.getUid())获取对应的子菜单列表,
// 再将子菜单列表设置为当前父菜单的子节点(通过 menu.setChildSubjects(childSubjects) 方法设置),以此构建出底部菜单的树形结构,展示父子菜单的层级关系。
for (MenuEntity menu : footerMenuList) {
List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid()); List<MenuEntity> childSubjects = menuMapper.findMenuByParentId(menu.getUid());
menu.setChildSubjects(childSubjects); menu.setChildSubjects(childSubjects);
} }
cacheService.setObject(TamguoConstant.FOOTER_INDEX_MENU, footerMenuList , 2 * 60 * 60); // 将构建好的底部菜单树形结构数据存入缓存中,缓存键名为 TamguoConstant.FOOTER_INDEX_MENU缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样在后续的 2 小时内,再次调用 findFooterMenus 方法时,就可以直接从缓存获取底部菜单数据,提高获取数据的效率,减少数据库查询操作。
cacheService.setObject(TamguoConstant.FOOTER_INDEX_MENU, footerMenuList, 2 * 60 * 60);
} }
// 最后返回获取到的底部菜单信息列表,其来源取决于缓存情况以及之前的逻辑执行结果,可能是缓存中的数据,也可能是新从数据库获取后存入缓存再返回的数据。
return footerMenuList; return footerMenuList;
} }
/**
* getMenuTree
* IMenuService getMenuTree
// 直接调用了 MenuMapper 的 selectList 方法,并传入 Condition.EMPTY 参数,这意味着使用默认的查询条件(通常是查询所有符合条件的记录,在这里就是所有菜单记录)
// 从数据库中获取菜单实体信息列表,该列表可以作为构建菜单树形结构或者其他菜单相关业务处理的基础数据,返回获取到的菜单实体列表。
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<MenuEntity> getMenuTree() { public List<MenuEntity> getMenuTree() {
return menuMapper.selectList(Condition.EMPTY); return menuMapper.selectList(Condition.EMPTY);
} }
/**
* findById
* IMenuService findById uid
// 直接调用了 MenuMapper 的 selectById 方法,传入菜单 UID 参数uid由 MenuMapper 具体实现与数据库的交互查询操作,返回查询到的菜单实体对象。
*/
@Override @Override
public MenuEntity findById(String uid) { public MenuEntity findById(String uid) {
return menuMapper.selectById(uid); return menuMapper.selectById(uid);
} }
} }

@ -22,97 +22,192 @@ import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils; import com.tamguo.util.ShiroUtils;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与试卷Paper相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service @Service
public class PaperService extends ServiceImpl<PaperMapper, PaperEntity> implements IPaperService{ public class PaperService extends ServiceImpl<PaperMapper, PaperEntity> implements IPaperService {
// 通过Spring的依赖注入机制自动注入 PaperMapper 接口的实现类实例,
// PaperMapper 是用于定义与试卷实体PaperEntity相关的数据库操作方法的接口比如查询、插入、更新、删除等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取试卷相关的数据信息。
@Autowired @Autowired
private PaperMapper paperMapper; private PaperMapper paperMapper;
// 同样通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在试卷相关业务中用于缓存不同类型的试卷列表数据,
// 以减少频繁访问数据库获取试卷信息的开销,提高系统性能,优化试卷相关业务操作的响应速度。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
// 通过依赖注入注入 QuestionMapper 接口的实现类实例QuestionMapper 应该是用于定义与试题相关的数据库操作方法的接口,
// 在涉及试卷中试题相关的操作(如添加、删除、更新试题信息等)时会调用它的方法来与数据库进行交互。
@Autowired @Autowired
private QuestionMapper questionMapper; private QuestionMapper questionMapper;
/**
* findHistoryPaper
* IPaperService findHistoryPaper
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<PaperEntity> findHistoryPaper() { public List<PaperEntity> findHistoryPaper() {
// 首先尝试从缓存中获取名为 TamguoConstant.HISTORY_PAPER 的缓存对象,并将其强制转换为 List<PaperEntity> 类型,
// 期望从缓存中获取到之前存储的历史试卷信息列表,如果缓存命中,就可以直接返回这个缓存中的试卷列表数据,减少数据库查询操作。
List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.HISTORY_PAPER); List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.HISTORY_PAPER);
if(paperList == null || paperList.isEmpty()){ // 判断从缓存中获取到的试卷列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的试卷信息),
Page<PaperEntity> page = new Page<>(1 , 6); // 如果满足这个条件,就需要从数据库中重新获取历史试卷信息列表,并将其存入缓存中供后续使用。
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.ZHENGTI_PAPER_ID , TamguoConstant.BEIJING_AREA_ID , page); if (paperList == null || paperList.isEmpty()) {
cacheService.setObject(TamguoConstant.ZHENGTI_PAPER_ID, paperList , 2 * 60 * 60); // 创建一个 Page 对象,用于分页查询,设置当前页码为 1每页显示的记录数为 6这里的分页参数可以根据实际业务需求调整
Page<PaperEntity> page = new Page<>(1, 6);
// 通过注入的 PaperMapper 的 findByTypeAndAreaId 方法传入特定的试卷类型标识符TamguoConstant.ZHENGTI_PAPER_ID和地区标识符TamguoConstant.BEIJING_AREA_ID以及分页对象page
// 从数据库中获取符合条件的历史试卷信息列表,这里的具体查询逻辑由 PaperMapper 中对应的方法实现来确定,返回查询到的历史试卷列表赋值给 paperList 变量。
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.ZHENGTI_PAPER_ID, TamguoConstant.BEIJING_AREA_ID, page);
// 将获取到的历史试卷列表存入缓存中,缓存的键名为 TamguoConstant.ZHENGTI_PAPER_ID同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findHistoryPaper 方法,就可以直接从缓存中获取历史试卷数据,而不用再次查询数据库了。
cacheService.setObject(TamguoConstant.ZHENGTI_PAPER_ID, paperList, 2 * 60 * 60);
} }
// 最后返回获取到的历史试卷信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况。
return paperList; return paperList;
} }
/**
* findSimulationPaper
* IPaperService findSimulationPaper 访
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<PaperEntity> findSimulationPaper() { public List<PaperEntity> findSimulationPaper() {
// 先从缓存中获取名为 TamguoConstant.SIMULATION_PAPER 的缓存对象,并强制转换为 List<PaperEntity> 类型,尝试获取之前缓存的模拟试卷信息列表,
// 如果缓存中有数据,就直接返回该缓存中的试卷列表数据,避免再次查询数据库。
List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.SIMULATION_PAPER); List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.SIMULATION_PAPER);
if(paperList == null || paperList.isEmpty()){ // 判断从缓存中获取到的模拟试卷列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取模拟试卷信息列表并缓存起来供后续使用。
Page<PaperEntity> page = new Page<>(1 , 6); if (paperList == null || paperList.isEmpty()) {
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.MONI_PAPER_ID , TamguoConstant.BEIJING_AREA_ID , page); // 创建一个 Page 对象用于分页查询,设置当前页码为 1每页显示记录数为 6可根据实际情况调整分页参数。
cacheService.setObject(TamguoConstant.SIMULATION_PAPER, paperList , 2 * 60 * 60); Page<PaperEntity> page = new Page<>(1, 6);
// 通过 PaperMapper 的 findByTypeAndAreaId 方法传入模拟试卷类型标识符TamguoConstant.MONI_PAPER_ID和地区标识符TamguoConstant.BEIJING_AREA_ID以及分页对象page
// 从数据库中获取符合条件的模拟试卷信息列表,具体查询逻辑由 PaperMapper 中对应方法实现来确定,将查询到的模拟试卷列表赋值给 paperList 变量。
paperList = paperMapper.findByTypeAndAreaId(TamguoConstant.MONI_PAPER_ID, TamguoConstant.BEIJING_AREA_ID, page);
// 将获取到的模拟试卷列表存入缓存中,缓存键名为 TamguoConstant.SIMULATION_PAPER缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 以便后续在有效期内再次调用 findSimulationPaper 方法时,能直接从缓存获取模拟试卷数据,提高获取数据的效率。
cacheService.setObject(TamguoConstant.SIMULATION_PAPER, paperList, 2 * 60 * 60);
} }
// 最后返回获取到的模拟试卷信息列表,其来源取决于缓存是否命中以及之前的逻辑执行情况,可能是缓存数据也可能是新从数据库获取后存入缓存再返回的数据。
return paperList; return paperList;
} }
/**
* findHotPaper String areaId
* IPaperService findHotPaper
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<PaperEntity> findHotPaper(String areaId) { public List<PaperEntity> findHotPaper(String areaId) {
// 首先从缓存中获取名为 TamguoConstant.HOT_PAPER 的缓存对象,并强制转换为 List<PaperEntity> 类型,尝试获取之前缓存的热门试卷信息列表,
// 这里有个特殊处理,先将获取到的列表赋值为 null不过通常来说这样的操作不太符合常规逻辑可能需要确认此处的真实意图是否合理
// 暂时按照现有代码逻辑继续分析,后续可能需要优化此处代码。
List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.HOT_PAPER); List<PaperEntity> paperList = (List<PaperEntity>) cacheService.getObject(TamguoConstant.HOT_PAPER);
paperList = null; paperList = null;
if(paperList == null || paperList.isEmpty()){ // 判断热门试卷列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取热门试卷信息列表并缓存起来供后续调用使用。
Page<PaperEntity> page = new Page<>(1 , 10); if (paperList == null || paperList.isEmpty()) {
paperList = paperMapper.findByAreaId(areaId ,page); // 创建一个 Page 对象用于分页查询,设置当前页码为 1每页显示记录数为 10可根据业务实际情况调整分页参数
cacheService.setObject(TamguoConstant.HOT_PAPER, paperList , 2 * 60 * 60); Page<PaperEntity> page = new Page<>(1, 10);
// 通过 PaperMapper 的 findByAreaId 方法传入地区标识符areaId和分页对象page从数据库中获取该地区的热门试卷信息列表
// 具体的热门试卷判定以及查询逻辑由 PaperMapper 中对应的方法实现来确定,将查询到的热门试卷列表赋值给 paperList 变量。
paperList = paperMapper.findByAreaId(areaId, page);
// 将构建好的热门试卷列表存入缓存中,缓存键名为 TamguoConstant.HOT_PAPER缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 这样在后续的 2 小时内,再次调用 findHotPaper 方法时,就可以直接从缓存获取热门试卷数据,提高获取数据的效率,减少数据库查询操作。
cacheService.setObject(TamguoConstant.HOT_PAPER, paperList, 2 * 60 * 60);
} }
// 最后返回获取到的热门试卷信息列表,其来源取决于缓存情况以及之前的逻辑执行结果,可能是缓存中的数据,也可能是新从数据库获取后存入缓存再返回的数据。
return paperList; return paperList;
} }
/**
* findList
* IPaperService findList ID ID
* Page 便
*/
@Override @Override
public Page<PaperEntity> findList(String subjectId , String courseId, public Page<PaperEntity> findList(String subjectId, String courseId,
String paperType, String year, String area , Integer pageNum) { String paperType, String year, String area, Integer pageNum) {
Page<PaperEntity> page = new Page<>(pageNum , TamguoConstant.DEFAULT_PAGE_SIZE); // 创建一个 Page 对象用于分页查询,设置当前页码为传入的 pageNum 参数值,每页显示的记录数为 TamguoConstant.DEFAULT_PAGE_SIZE这应该是预定义的每页默认显示记录数常量
if("0".equals(courseId)) { Page<PaperEntity> page = new Page<>(pageNum, TamguoConstant.DEFAULT_PAGE_SIZE);
// 判断课程 ID 是否为 "0",如果是,将其设置为空字符串,可能是表示不根据课程 ID 进行筛选的情况,具体业务含义取决于项目设计。
if ("0".equals(courseId)) {
courseId = ""; courseId = "";
} }
if("0".equals(paperType)) { // 类似地,判断试卷类型是否为 "0",如果是,将其设置为空字符串,意味着不依据试卷类型进行筛选,具体根据业务逻辑来确定这样处理的意义。
if ("0".equals(paperType)) {
paperType = ""; paperType = "";
} }
if("0".equals(year)) { // 判断年份是否为 "0",若是则设置为空字符串,可能表示不按照年份来筛选试卷,同样取决于业务中对年份筛选的具体规则。
if ("0".equals(year)) {
year = ""; year = "";
} }
if("0".equals(area)) { // 判断地区是否为 "0",若是则设置为空字符串,即不通过地区来筛选试卷,这与业务中地区作为筛选条件的具体定义相关。
if ("0".equals(area)) {
area = ""; area = "";
} }
return page.setRecords(paperMapper.findList(subjectId , courseId , paperType , year , area , page)); // 通过 PaperMapper 的 findList 方法,传入学科 ID、处理后的课程 ID、试卷类型、年份、地区以及分页对象page这些参数
// 从数据库中获取符合条件的试卷信息列表,并将结果设置到 Page 对象中(通过 page.setRecords 方法),最后返回这个包含查询结果的 Page 对象,
// 方便后续进行分页相关的业务操作,如在前端展示分页后的试卷列表等。
return page.setRecords(paperMapper.findList(subjectId, courseId, paperType, year, area, page));
} }
/**
* find
* IPaperService find paperId
// 直接调用了 PaperMapper 的 selectById 方法,传入试卷 UID 参数paperId由 PaperMapper 具体实现与数据库的交互查询操作,返回查询到的试卷实体对象。
*/
@Override @Override
public PaperEntity find(String paperId) { public PaperEntity find(String paperId) {
return paperMapper.selectById(paperId); return paperMapper.selectById(paperId);
} }
/**
* findPaperByAreaId
* IPaperService findPaperByAreaId ID
// 根据传入的 type 参数值判断不同的情况,如果 type 参数值为 "n",则调用本类中的 findHotPaper 方法(传入 areaId 参数)获取热门试卷信息列表并返回;
// 如果不是 "n",则创建一个 Page 对象用于分页查询(设置当前页码为 1每页显示记录数为 8然后通过 PaperMapper 的 findPaperByAreaId 方法,传入地区 ID、试卷类型以及分页对象
// 从数据库中获取符合条件的试卷信息列表并返回,具体的查询逻辑由 PaperMapper 中对应的方法实现来确定。
*/
@Override @Override
public List<PaperEntity> findPaperByAreaId(String areaId , String type) { public List<PaperEntity> findPaperByAreaId(String areaId, String type) {
if("n".equals(type)){ if ("n".equals(type)) {
return this.findHotPaper(areaId); return this.findHotPaper(areaId);
} }
Page<PaperEntity> page = new Page<>(1 , 8); Page<PaperEntity> page = new Page<>(1, 8);
return paperMapper.findPaperByAreaId(areaId , type , page); return paperMapper.findPaperByAreaId(areaId, type, page);
} }
/**
* getPaperTotal
* IPaperService getPaperTotal
// 直接调用了 PaperMapper 的 getPaperTotal 方法,由 PaperMapper 具体实现查询数据库中试卷总记录数的逻辑,返回查询到的试卷总数量(类型为 Long
*/
@Override @Override
public Long getPaperTotal() { public Long getPaperTotal() {
return paperMapper.getPaperTotal(); return paperMapper.getPaperTotal();
} }
/**
* findByCreaterId
* IPaperService findByCreaterId createrId
// 直接调用了 PaperMapper 的 findByCreaterId 方法,传入创建者 ID 参数createrId由 PaperMapper 具体实现与数据库的交互查询操作,返回查询到的试卷列表。
*/
@Override @Override
public List<PaperEntity> findByCreaterId(String createrId) { public List<PaperEntity> findByCreaterId(String createrId) {
return paperMapper.findByCreaterId(createrId); return paperMapper.findByCreaterId(createrId);
} }
@Transactional(readOnly=false) /**
* updatePaperName
* IPaperService updatePaperName
// 首先通过 PaperMapper 的 selectById 方法根据传入的试卷 IDpaperId从数据库中查询获取对应的试卷实体
// 然后将试卷实体的名称属性name设置为传入的新名称name最后通过 PaperMapper 的 updateById 方法将更新后的试卷实体信息更新到数据库中,完成试卷名称的修改操作。
*/
@Transactional(readOnly = false)
@Override @Override
public void updatePaperName(String paperId, String name) { public void updatePaperName(String paperId, String name) {
PaperEntity paper = paperMapper.selectById(paperId); PaperEntity paper = paperMapper.selectById(paperId);
@ -120,122 +215,15 @@ public class PaperService extends ServiceImpl<PaperMapper, PaperEntity> implemen
paperMapper.updateById(paper); paperMapper.updateById(paper);
} }
/**
* deletePaper
* IPaperService deletePaper @Transactional(readOnly = false)
// 因为涉及到对数据库中试卷和试题数据的删除操作,需要保证事务的完整性,要么全部操作成功,要么全部回滚。
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public Result deletePaper(String paperId) { public Result deletePaper(String paperId) {
// 首先通过 PaperMapper 的 selectById 方法根据传入的试卷 IDpaperId从数据库中查询获取对应的试卷实体
PaperEntity paper = paperMapper.selectById(paperId); PaperEntity paper = paperMapper.selectById(paperId);
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) { // 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
return Result.result(501, null , "不能删除其他人的试卷!"); //
}
paperMapper.deleteById(paperId);
// 删除试题
questionMapper.delete(Condition.create().eq("paper_id", paperId));
return Result.result(Result.SUCCESS_CODE, null , "删除成功!");
}
@Transactional(readOnly=false)
@Override
public void addPaperQuestionInfo(String paperId, String title,
String name, String type) {
PaperEntity paper = paperMapper.selectById(paperId);
String questionInfo = paper.getQuestionInfo();
JSONArray qList = JSONArray.parseArray(questionInfo);
JSONObject entity = new JSONObject();
entity.put("name", name);
entity.put("title", title);
entity.put("type", type);
entity.put("uid", UUID.randomUUID().toString());
qList.add(entity);
paper.setQuestionInfo(qList.toString());
paperMapper.updateById(paper);
}
@Transactional(readOnly=false)
@Override
public void updatePaperQuestionInfo(String paperId, String title,
String name, String type , String uid) {
PaperEntity paper = paperMapper.selectById(paperId);
JSONArray qList = JSONArray.parseArray(paper.getQuestionInfo());
for(int i =0 ; i<qList.size() ; i++){
JSONObject q = qList.getJSONObject(i);
if(q.getString("uid").equals(uid)){
q.put("name", name);
q.put("title", title);
q.put("type", type);
}
}
paper.setQuestionInfo(qList.toString());
paperMapper.updateById(paper);
}
@Override
public Result deletePaperQuestionInfoBtn(String paperId, String uid) {
PaperEntity paper = paperMapper.selectById(paperId);
if(!paper.getCreaterId().equals(ShiroUtils.getMember().getUid())) {
return Result.failResult("试卷属于当前用户,不能修改!");
}
JSONArray qList = JSONArray.parseArray(paper.getQuestionInfo());
for(int i =0 ; i<qList.size() ; i++){
JSONObject q = qList.getJSONObject(i);
if(q.getString("uid").equals(uid)){
qList.remove(i);
}
}
paper.setQuestionInfo(qList.toString());
paperMapper.updateById(paper);
return Result.result(Result.SUCCESS_CODE, null, "删除子卷成功");
}
@Override
public Page<PaperEntity> memberPaperList(String name , String memberId , Page<PaperEntity> page) {
if(!StringUtils.isEmpty(name)){
name = "%" + name + "%";
}
return page.setRecords(paperMapper.queryPageByNameAndCreatorId(name , memberId , page));
}
@Transactional(readOnly=false)
@Override
public void addPaper(PaperEntity paper) {
paper.setDownHits(0);
paper.setOpenHits(0);
paper.setQuestionInfo("[]");
// 写入seo信息
paper.setSeoTitle(paper.getName());
paper.setSeoKeywords(paper.getName());
paper.setSeoDescription(paper.getName());
paperMapper.insert(paper);
}
@Transactional(readOnly=true)
@Override
public List<PaperEntity> featuredPaper(String type, String subjectId) {
Page<PaperEntity> page = new Page<>(1,8);
return paperMapper.featuredPaper(type , subjectId , page);
}
@Transactional(readOnly=true)
@Override
public List<PaperEntity> findHotPaper(String subjectId, String courseId) {
Page<PaperEntity> page = new Page<>(1,5);
return paperMapper.findHotPaper(subjectId , courseId , page);
}
@Transactional(readOnly=false)
@Override
public Result updatePaper(PaperEntity paper) {
PaperEntity entity = paperMapper.selectById(paper.getUid());
if(!entity.getCreaterId().equals(ShiroUtils.getMember().getUid())) {
return Result.failResult("试卷属于当前用户,不能修改!");
}
paper.setCreaterId(ShiroUtils.getUserId());
paperMapper.updateById(paper);
return Result.result(Result.SUCCESS_CODE, paper, "修改成功");
}
}

@ -9,6 +9,7 @@ import org.springframework.transaction.annotation.Transactional;
import com.baomidou.mybatisplus.plugins.Page; import com.baomidou.mybatisplus.plugins.Page;
import com.baomidou.mybatisplus.service.impl.ServiceImpl; import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import com.tamguo.dao.PaperMapper; import com.tamguo.dao.PaperMapper;
import com.baomidou.mybatisplus.plugins.Page;
import com.tamguo.dao.QuestionMapper; import com.tamguo.dao.QuestionMapper;
import com.tamguo.model.PaperEntity; import com.tamguo.model.PaperEntity;
import com.tamguo.model.QuestionEntity; import com.tamguo.model.QuestionEntity;
@ -16,88 +17,176 @@ import com.tamguo.service.IQuestionService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils; import com.tamguo.util.ShiroUtils;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理与试题Question相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循了Spring的分层架构设计规范。
@Service @Service
public class QuestionService extends ServiceImpl<QuestionMapper, QuestionEntity> implements IQuestionService{ public class QuestionService extends ServiceImpl<QuestionMapper, QuestionEntity> implements IQuestionService {
// 通过Spring的依赖注入机制自动注入 QuestionMapper 接口的实现类实例,
// QuestionMapper 是用于定义与试题实体QuestionEntity相关的数据库操作方法的接口例如查询、插入、更新、删除等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取试题相关的数据信息。
@Autowired @Autowired
private QuestionMapper questionMapper; private QuestionMapper questionMapper;
// 同样通过依赖注入注入 PaperMapper 接口的实现类实例PaperMapper 用于定义与试卷实体PaperEntity相关的数据库操作方法
// 在涉及试题与试卷关联相关的业务逻辑(如判断试题所属试卷的创建者等情况)时,会调用它的方法来获取试卷相关信息,辅助试题业务的处理。
@Autowired @Autowired
private PaperMapper paperMapper; private PaperMapper paperMapper;
/**
* findByChapterId
* IQuestionService findByChapterId chapterId
* Page 便
*/
@Override @Override
public Page<QuestionEntity> findByChapterId(String chapterId , Page<QuestionEntity> page) { public Page<QuestionEntity> findByChapterId(String chapterId, Page<QuestionEntity> page) {
return page.setRecords(questionMapper.findByChapterId(chapterId , page)); // 通过 QuestionMapper 的 findByChapterId 方法,传入章节 IDchapterId和分页对象page
// 从数据库中获取该章节下的试题信息列表,并将结果设置到传入的 Page 对象中(通过 page.setRecords 方法),
// 最后返回这个包含查询结果的 Page 对象,以便后续进行分页相关的业务操作,比如在前端展示该章节下分页后的试题列表。
return page.setRecords(questionMapper.findByChapterId(chapterId, page));
} }
@Transactional(readOnly=true) /**
* findNormalQuestion
* IQuestionService findNormalQuestion @Transactional(readOnly = true)
// 因为只是进行查询操作,不会对数据库中的数据进行修改,使用只读事务可以提高查询性能,同时避免不必要的数据库锁等问题,
// 直接调用了 QuestionMapper 的 findNormalQuestion 方法传入试题的唯一标识符uid由 QuestionMapper 具体实现与数据库的交互查询操作,返回查询到的试题实体对象。
*/
@Transactional(readOnly = true)
@Override @Override
public QuestionEntity findNormalQuestion(String uid) { public QuestionEntity findNormalQuestion(String uid) {
return questionMapper.findNormalQuestion(uid); return questionMapper.findNormalQuestion(uid);
} }
/**
* findPaperQuestion
* IQuestionService findPaperQuestion
// 直接调用了 QuestionMapper 的 findByPaperId 方法传入试卷的唯一标识符paperId由 QuestionMapper 具体实现与数据库的交互查询操作,返回查询到的该试卷包含的试题列表。
*/
@Override @Override
public List<QuestionEntity> findPaperQuestion(String paperId) { public List<QuestionEntity> findPaperQuestion(String paperId) {
return questionMapper.findByPaperId(paperId); return questionMapper.findByPaperId(paperId);
} }
/**
* select
* IQuestionService select questionId
// 直接调用了 QuestionMapper 的 selectByUid 方法,传入试题 UID 参数questionId由 QuestionMapper 具体实现与数据库的交互查询操作,返回查询到的试题实体对象。
*/
@Override @Override
public QuestionEntity select(String questionId) { public QuestionEntity select(String questionId) {
return questionMapper.selectByUid(questionId); return questionMapper.selectByUid(questionId);
} }
@Transactional(readOnly=false) /**
* addQuestion
* IQuestionService addQuestion @Transactional(readOnly = false)
// 因为涉及到向数据库插入试题数据的操作,需要保证事务的完整性,确保插入操作要么全部成功,要么全部回滚,避免出现数据不一致的情况。
*/
@Transactional(readOnly = false)
@Override @Override
public Result addQuestion(QuestionEntity question) { public Result addQuestion(QuestionEntity question) {
// 首先通过 PaperMapper 的 selectById 方法,根据传入的试题所属试卷的 IDquestion.getPaperId().toString())从数据库中查询获取对应的试卷实体,
// 这是为了后续判断当前用户是否有权限向该试卷添加试题,需要确认试卷的创建者信息。
PaperEntity paper = paperMapper.selectById(question.getPaperId().toString()); PaperEntity paper = paperMapper.selectById(question.getPaperId().toString());
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) { // 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
// 则返回相应的错误提示结果(表示该试卷不属于当前用户,无权添加试题),将错误提示信息封装在 Result 对象中返回给调用者。
if (!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null, "改试卷不属于您!"); return Result.result(501, null, "改试卷不属于您!");
} }
// 如果当前用户是试卷的创建者,将试题的课程 ID 设置为试卷的课程 ID确保试题与所属试卷在课程方面的关联性
question.setCourseId(paper.getCourseId()); question.setCourseId(paper.getCourseId());
// 通过 QuestionMapper 的 insert 方法将试题实体信息插入到数据库中,完成试题添加操作,
// 最后返回添加成功的提示结果,将成功信息封装在 Result 对象中返回给调用者,表示试题添加成功。
questionMapper.insert(question); questionMapper.insert(question);
return Result.result(0, null, "添加成功!"); return Result.result(0, null, "添加成功!");
} }
@Transactional(readOnly=true) /**
* queryQuestionList
* IQuestionService queryQuestionList ID
* Page
*/
@Transactional(readOnly = true)
@Override @Override
public Page<QuestionEntity> queryQuestionList(String questionType , String uid , String content , String paperId , public Page<QuestionEntity> queryQuestionList(String questionType, String uid, String content, String paperId,
Page<QuestionEntity> page) { Page<QuestionEntity> page) {
if(!ShiroUtils.getUserId().equals(paperMapper.selectById(paperId).getCreaterId())) { // 通过 PaperMapper 的 selectById 方法,根据传入的试卷 IDpaperId从数据库中查询获取对应的试卷实体
// 然后获取试卷的创建者 ID并与当前用户的 ID通过 ShiroUtils 获取)进行比对,如果当前用户不是试卷的创建者,
// 则直接返回一个空的记录列表(通过 page.setRecords(null) 设置 Page 对象的记录列表为空),表示无权查询该试卷下的试题信息。
if (!ShiroUtils.getUserId().equals(paperMapper.selectById(paperId).getCreaterId())) {
return page.setRecords(null); return page.setRecords(null);
} }
if(!StringUtils.isEmpty(content)){ // 判断传入的试题内容content是否为空字符串如果不为空则在内容前后添加 "%" 符号,用于构建模糊查询的条件,
// 可能在数据库查询中会根据这个模糊条件查找包含指定内容的试题信息,具体取决于数据库查询语句的实现逻辑。
if (!StringUtils.isEmpty(content)) {
content = "%" + content + "%"; content = "%" + content + "%";
} }
return page.setRecords(questionMapper.queryQuestionList(questionType, uid , content , paperId , page)); // 通过 QuestionMapper 的 queryQuestionList 方法,传入试题类型、试题唯一标识符、处理后的试题内容、试卷 ID 以及分页对象page这些参数
// 从数据库中获取符合条件的试题信息列表,并将结果设置到 Page 对象中(通过 page.setRecords 方法),
// 最后返回这个包含查询结果的 Page 对象,方便后续进行分页相关的业务操作,如在前端展示分页后的试题列表等。
return page.setRecords(questionMapper.queryQuestionList(questionType, uid, content, paperId, page));
} }
@Transactional(readOnly=false) /**
* updateQuestion
* IQuestionService updateQuestion @Transactional(readOnly = false)
// 因为涉及到对数据库中试题数据的更新操作,需要保证事务的完整性,确保更新操作要么全部成功,要么全部回滚,避免出现数据不一致的情况。
*/
@Transactional(readOnly = false)
@Override @Override
public Result updateQuestion(QuestionEntity question) { public Result updateQuestion(QuestionEntity question) {
// 首先通过 PaperMapper 的 selectById 方法,根据传入的试题所属试卷的 IDquestion.getPaperId().toString())从数据库中查询获取对应的试卷实体,
// 这是为了后续判断当前用户是否有权限更新该试卷下的试题信息,需要确认试卷的创建者信息。
PaperEntity paper = paperMapper.selectById(question.getPaperId().toString()); PaperEntity paper = paperMapper.selectById(question.getPaperId().toString());
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) { // 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
// 则返回相应的错误提示结果(表示该试卷不属于当前用户,无权更新试题),将错误提示信息封装在 Result 对象中返回给调用者。
if (!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null, "改试卷不属于您!"); return Result.result(501, null, "改试卷不属于您!");
} }
// 如果当前用户是试卷的创建者,通过 QuestionMapper 的 updateById 方法,将传入的更新后的试题实体信息更新到数据库中,完成试题更新操作,
// 最后返回更新成功的提示结果,将成功信息封装在 Result 对象中返回给调用者,表示试题更新成功。
questionMapper.updateById(question); questionMapper.updateById(question);
return Result.result(0, null, "修改成功!"); return Result.result(0, null, "修改成功!");
} }
@Transactional(readOnly=false) /**
* delete
* IQuestionService delete @Transactional(readOnly = false)
// 因为涉及到对数据库中试题数据的删除操作,需要保证事务的完整性,确保删除操作要么全部成功,要么全部回滚,避免出现数据不一致的情况。
*/
@Transactional(readOnly = false)
@Override @Override
public Result delete(String uid) { public Result delete(String uid) {
// 首先通过 QuestionMapper 的 selectById 方法根据传入的试题的唯一标识符uid从数据库中查询获取对应的试题实体
QuestionEntity question = questionMapper.selectById(uid); QuestionEntity question = questionMapper.selectById(uid);
// 接着通过 PaperMapper 的 selectById 方法,根据试题所属试卷的 IDquestion.getPaperId().toString())从数据库中查询获取对应的试卷实体,
// 这是为了判断当前用户是否有权限删除该试卷下的试题,需要确认试卷的创建者信息。
PaperEntity paper = paperMapper.selectById(question.getPaperId().toString()); PaperEntity paper = paperMapper.selectById(question.getPaperId().toString());
if(!ShiroUtils.getUserId().equals(paper.getCreaterId())) { // 通过 ShiroUtils 获取当前用户的 ID并与试卷的创建者 ID 进行比对,如果当前用户不是试卷的创建者,
// 则返回相应的错误提示结果(表示该试卷不属于当前用户,无权删除试题),将错误提示信息封装在 Result 对象中返回给调用者。
if (!ShiroUtils.getUserId().equals(paper.getCreaterId())) {
return Result.result(501, null, "改试卷不属于您!"); return Result.result(501, null, "改试卷不属于您!");
} }
// 如果当前用户是试卷的创建者,通过 QuestionMapper 的 deleteById 方法根据试题的唯一标识符uid从数据库中删除对应的试题信息完成试题删除操作
// 最后返回删除成功的提示结果,将成功信息封装在 Result 对象中返回给调用者,表示试题删除成功。
questionMapper.deleteById(uid); questionMapper.deleteById(uid);
return Result.result(0, null, "删除成功!"); return Result.result(0, null, "删除成功!");
} }
@Transactional(readOnly=true) /**
* featuredQuestion
* IQuestionService featuredQuestion @Transactional(readOnly = true)
// 因为只是进行查询操作,不会对数据库中的数据进行修改,使用只读事务可以提高查询性能,同时避免不必要的数据库锁等问题。
*/
@Transactional(readOnly = true)
@Override @Override
public List<QuestionEntity> featuredQuestion(String subjectId, String courseId) { public List<QuestionEntity> featuredQuestion(String subjectId, String courseId) {
Page<QuestionEntity> page = new Page<>(1 , 5); // 创建一个 Page 对象用于分页查询,设置当前页码为 1每页显示记录数为 5这里的分页参数可以根据实际业务需求调整
return questionMapper.featuredQuestion(subjectId , courseId , page); Page<QuestionEntity> page = new Page<>(1, 5);
// 通过 QuestionMapper 的 featuredQuestion 方法,传入学科 IDsubjectId和课程 IDcourseId以及分页对象page
// 从数据库中获取符合条件的特色试题信息列表,具体的特色试题判定以及查询逻辑由 QuestionMapper 中对应的方法实现来确定,
// 最后返回查询到的特色试题列表,方便在业务中进行展示、推荐等相关操作。
return questionMapper.featuredQuestion(subjectId, courseId, page);
} }
} }

@ -19,54 +19,105 @@ import com.tamguo.model.SchoolEntity;
import com.tamguo.service.ISchoolService; import com.tamguo.service.ISchoolService;
import com.tamguo.util.TamguoConstant; import com.tamguo.util.TamguoConstant;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件表明它主要负责处理与学校School相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service @Service
public class SchoolService extends ServiceImpl<SchoolMapper, SchoolEntity> implements ISchoolService { public class SchoolService extends ServiceImpl<SchoolMapper, SchoolEntity> implements ISchoolService {
// 通过Spring的依赖注入机制自动注入 SchoolMapper 接口的实现类实例,
// SchoolMapper 是用于定义与学校实体SchoolEntity相关的数据库操作方法的接口例如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取学校相关的数据信息。
@Autowired @Autowired
private SchoolMapper schoolMapper; private SchoolMapper schoolMapper;
// 同样通过依赖注入注入 PaperMapper 接口的实现类实例PaperMapper 用于定义与试卷实体PaperEntity相关的数据库操作方法
// 在涉及学校与试卷关联相关的业务逻辑(如获取某个学校的试卷列表等情况)时,会调用它的方法来获取试卷相关信息,辅助学校业务的处理。
@Autowired @Autowired
private PaperMapper paperMapper; private PaperMapper paperMapper;
// 通过依赖注入注入 CacheService 实例CacheService 大概率是用于处理缓存相关操作的服务类,
// 例如从缓存中获取数据、将数据存入缓存以及判断缓存是否存在等功能,在学校相关业务中用于缓存不同类型的学校列表数据以及关联的试卷数据等,
// 以减少频繁访问数据库获取学校及相关信息的开销,提高系统性能,优化学校相关业务操作的响应速度。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
/**
* findEliteSchoolPaper
* ISchoolService findEliteSchoolPaper
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<SchoolEntity> findEliteSchoolPaper(String shcoolId) { public List<SchoolEntity> findEliteSchoolPaper(String shcoolId) {
// 首先尝试从缓存中获取名为 TamguoConstant.ELITE_SCHOOL_PAPER 的缓存对象,并将其强制转换为 List<SchoolEntity> 类型,
// 期望从缓存中获取到之前存储的名校试卷相关的学校信息列表,如果缓存命中,就可以直接返回这个缓存中的学校列表数据,减少数据库查询操作。
List<SchoolEntity> schoolList = (List<SchoolEntity>) cacheService.getObject(TamguoConstant.ELITE_SCHOOL_PAPER); List<SchoolEntity> schoolList = (List<SchoolEntity>) cacheService.getObject(TamguoConstant.ELITE_SCHOOL_PAPER);
// 获取名校试卷 // 判断从缓存中获取到的学校列表是否为 null 或者为空列表(即没有缓存数据或者缓存数据不存在有效的学校信息),
if(schoolList == null || schoolList.isEmpty()){ // 如果满足这个条件,就需要从数据库中重新获取名校试卷相关的学校信息列表,并构建学校与试卷的关联信息,然后将其存入缓存中供后续使用。
if (schoolList == null || schoolList.isEmpty()) {
// 创建一个 Page 对象用于分页查询学校信息,设置当前页码为 1每页显示的记录数为 3这里的分页参数可以根据实际业务需求调整
Page<SchoolEntity> page = new Page<>(); Page<SchoolEntity> page = new Page<>();
page.setCurrent(1); page.setCurrent(1);
page.setSize(3); page.setSize(3);
schoolList = schoolMapper.findByAreaId(shcoolId , page); // 通过注入的 SchoolMapper 的 findByAreaId 方法,传入学校 IDshcoolId和分页对象page
for(SchoolEntity school : schoolList){ // 从数据库中获取符合条件的学校信息列表,这里的具体查询逻辑由 SchoolMapper 中对应的方法实现来确定,返回查询到的学校列表赋值给 schoolList 变量。
schoolList = schoolMapper.findByAreaId(shcoolId, page);
// 遍历获取到的学校列表对于每个学校school需要获取其对应的试卷列表信息并进行关联设置。
for (SchoolEntity school : schoolList) {
// 创建一个新的 Page 对象用于分页查询试卷信息,设置当前页码为 1每页显示记录数为 3同样可根据实际情况调整分页参数。
Page<PaperEntity> p = new Page<>(); Page<PaperEntity> p = new Page<>();
p.setCurrent(1); p.setCurrent(1);
p.setSize(3); p.setSize(3);
List<PaperEntity> paperList = paperMapper.findBySchoolId(school.getUid() , p); // 通过 PaperMapper 的 findBySchoolId 方法传入当前学校的唯一标识符school.getUid()和分页对象p
// 从数据库中获取该学校对应的试卷信息列表,具体的查询逻辑由 PaperMapper 中对应方法实现来确定,将查询到的试卷列表赋值给 school 的 paperList 属性,
// 这样就构建了学校与试卷的关联关系,每个学校实体中包含了其对应的试卷列表信息。
List<PaperEntity> paperList = paperMapper.findBySchoolId(school.getUid(), p);
school.setPaperList(paperList); school.setPaperList(paperList);
} }
cacheService.setObject(TamguoConstant.ELITE_SCHOOL_PAPER, schoolList , 2 * 60 * 60); // 将构建好的包含学校与试卷关联信息的学校列表存入缓存中,缓存的键名为 TamguoConstant.ELITE_SCHOOL_PAPER同时设置了缓存的有效时间为 2 * 60 * 60 秒(即 2 小时),
// 这样在接下来的 2 小时内,如果再次调用 findEliteSchoolPaper 方法,就可以直接从缓存中获取名校试卷相关的学校数据,而不用再次查询数据库了。
cacheService.setObject(TamguoConstant.ELITE_SCHOOL_PAPER, schoolList, 2 * 60 * 60);
} }
// 最后返回获取到的学校信息列表,这个列表可能是从缓存中直接获取的,也可能是从数据库中查询后存入缓存再返回的,
// 取决于缓存中是否存在有效数据以及之前的逻辑执行情况,每个学校实体中包含了对应的试卷列表信息,方便在业务中进行展示、操作等处理。
return schoolList; return schoolList;
} }
/**
* findEliteSchool
* ISchoolService findEliteSchool 访
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Override @Override
public List<SchoolEntity> findEliteSchool() { public List<SchoolEntity> findEliteSchool() {
// 先从缓存中获取名为 TamguoConstant.ELITE_PAPER 的缓存对象,并强制转换为 List<SchoolEntity> 类型,尝试获取之前缓存的名校信息列表,
// 如果缓存中有数据,就直接返回该缓存中的学校列表数据,避免再次查询数据库。
List<SchoolEntity> schoolList = (List<SchoolEntity>) cacheService.getObject(TamguoConstant.ELITE_PAPER); List<SchoolEntity> schoolList = (List<SchoolEntity>) cacheService.getObject(TamguoConstant.ELITE_PAPER);
if(schoolList == null || schoolList.isEmpty()){ // 判断从缓存中获取到的学校列表是否为 null 或者为空,如果是这种情况,就需要从数据库中重新获取名校信息列表并缓存起来供后续使用。
RowBounds row = new RowBounds(1 , 6); if (schoolList == null || schoolList.isEmpty()) {
// 创建一个 RowBounds 对象用于设置分页参数,这里设置偏移量为 1表示从第 1 条记录开始获取,通常第 1 条记录的索引是 0所以实际是获取第 2 条开始的数据,不过具体含义可能根据数据库实现有差异),
// 每页获取的记录数为 6可根据实际业务需求调整用于在数据库查询中进行分页控制。
RowBounds row = new RowBounds(1, 6);
// 通过 SchoolMapper 的 selectPage 方法,传入 RowBounds 对象row和 Condition.EMPTY表示使用默认的查询条件通常是查询所有符合条件的记录
// 从数据库中获取符合条件的名校信息列表,具体的名校判定以及查询逻辑由 SchoolMapper 中对应的方法实现来确定,将查询到的名校列表赋值给 schoolList 变量。
schoolList = schoolMapper.selectPage(row, Condition.EMPTY); schoolList = schoolMapper.selectPage(row, Condition.EMPTY);
cacheService.setObject(TamguoConstant.ELITE_PAPER, schoolList , 2 * 60 * 60); // 将获取到的名校列表存入缓存中,缓存键名为 TamguoConstant.ELITE_PAPER缓存有效时间设置为 2 * 60 * 60 秒2 小时),
// 以便后续在有效期内再次调用 findEliteSchool 方法时,能直接从缓存获取名校数据,提高获取数据的效率。
cacheService.setObject(TamguoConstant.ELITE_PAPER, schoolList, 2 * 60 * 60);
} }
// 最后返回获取到的名校信息列表,其来源取决于缓存是否命中以及之前的逻辑执行情况,可能是缓存数据也可能是新从数据库获取后存入缓存再返回的数据。
return schoolList; return schoolList;
} }
@Transactional(readOnly=true) /**
* findSchoolByAreaId
* ISchoolService findSchoolByAreaId ID ID
// 该方法被标记为只读事务(通过 @Transactional(readOnly = true) 注解指定),因为只是进行查询操作,不会对数据库中的数据进行修改,使用只读事务可以提高查询性能,同时避免不必要的数据库锁等问题。
*/
@Transactional(readOnly = true)
@Override @Override
public List<SchoolEntity> findSchoolByAreaId(String areaId) { public List<SchoolEntity> findSchoolByAreaId(String areaId) {
// 将传入的以逗号分隔的地区 ID 字符串进行分割,得到一个包含各个地区 ID 的字符串数组,
// 然后通过 Arrays.asList 方法将其转换为 List<String> 类型,以便后续作为参数传递给 SchoolMapper 的查询方法,用于根据地区 ID 列表查询学校信息。
return schoolMapper.findByAreaIds(Arrays.asList(areaId.split(","))); return schoolMapper.findByAreaIds(Arrays.asList(areaId.split(",")));
} }
} }

@ -14,76 +14,132 @@ import com.tamguo.model.CourseEntity;
import com.tamguo.model.SubjectEntity; import com.tamguo.model.SubjectEntity;
import com.tamguo.service.ISubjectService; import com.tamguo.service.ISubjectService;
// 使用 @Service 注解将这个类标记为Spring中的服务层组件意味着它主要负责处理与学科Subject相关的业务逻辑
// Spring会自动扫描并将其纳入到Spring容器管理中方便在其他地方进行依赖注入等操作遵循Spring的分层架构设计。
@Service @Service
public class SubjectService extends ServiceImpl<SubjectMapper, SubjectEntity> implements ISubjectService{ public class SubjectService extends ServiceImpl<SubjectMapper, SubjectEntity> implements ISubjectService {
// 通过Spring的依赖注入机制自动注入 SubjectMapper 接口的实现类实例,
// SubjectMapper 是用于定义与学科实体SubjectEntity相关的数据库操作方法的接口比如查询、插入、更新等操作
// 在本服务类中会调用它的方法来与数据库进行交互,获取学科相关的数据信息。
@Autowired @Autowired
private SubjectMapper subjectMapper; private SubjectMapper subjectMapper;
// 同样通过依赖注入注入 CourseMapper 接口的实现类实例CourseMapper 用于定义与课程CourseEntity相关的数据库操作方法
// 在涉及学科与课程关联相关的业务逻辑(如获取某个学科下的课程列表等情况)时,会调用它的方法来获取课程相关信息,辅助学科业务的处理。
@Autowired @Autowired
private CourseMapper courseMapper; private CourseMapper courseMapper;
/**
* find
* ISubjectService find uid
*/
@Override @Override
public SubjectEntity find(String uid) { public SubjectEntity find(String uid) {
// 通过 SubjectMapper 的 selectById 方法,根据传入的学科 IDuid从数据库中查询获取对应的学科实体对象这一步获取到了基本的学科信息。
SubjectEntity subject = subjectMapper.selectById(uid); SubjectEntity subject = subjectMapper.selectById(uid);
// 通过 CourseMapper 的 findBySubjectId 方法,传入学科 IDuid从数据库中查询获取该学科下的课程列表信息得到一个包含课程实体对象的列表。
List<CourseEntity> courseList = courseMapper.findBySubjectId(uid); List<CourseEntity> courseList = courseMapper.findBySubjectId(uid);
// 将查询到的课程列表信息设置到学科实体对象的 courseList 属性中,这样返回的学科对象就包含了其关联的课程信息,方便后续在业务中进行统一处理和展示等操作。
subject.setCourseList(courseList); subject.setCourseList(courseList);
return subject; return subject;
} }
/**
* getCourseTree
* JSONArray
*/
@Override @Override
public JSONArray getCourseTree() { public JSONArray getCourseTree() {
// 创建一个空的 JSONArray 对象,用于存储最终构建好的课程树形结构数据,后续会不断向其中添加节点信息来构建完整的树形结构。
JSONArray courseTree = new JSONArray(); JSONArray courseTree = new JSONArray();
// 通过 SubjectMapper 的 selectList 方法,传入 Condition.EMPTY表示使用默认的查询条件通常意味着查询所有符合条件的记录在这里就是所有学科记录
// 从数据库中获取所有的学科信息列表,得到一个包含所有学科实体对象的列表,用于后续构建树形结构时作为顶层节点(父节点)使用。
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<SubjectEntity> subjectList = subjectMapper.selectList(Condition.EMPTY); List<SubjectEntity> subjectList = subjectMapper.selectList(Condition.EMPTY);
for(int i=0 ; i<subjectList.size() ; i++){
// 遍历获取到的学科列表对于每个学科subject都要在树形结构中创建对应的节点信息并添加到 courseTree 中,同时还要添加其下的课程节点信息作为子节点。
for (int i = 0; i < subjectList.size(); i++) {
SubjectEntity subject = subjectList.get(i); SubjectEntity subject = subjectList.get(i);
// 创建一个新的 JSONObject 对象,用于表示当前学科在树形结构中的节点信息,后续会向其中设置各种属性,如唯一标识符、名称、父节点 ID 等。
JSONObject node = new JSONObject(); JSONObject node = new JSONObject();
// 设置当前学科节点的唯一标识符这里在学科原有的唯一标识符subject.getUid())前添加了 "s",可能是为了在整个树形结构中区分学科节点和课程节点,或者遵循特定的业务标识规则。
node.put("uid", "s" + subject.getUid()); node.put("uid", "s" + subject.getUid());
// 设置当前学科节点的名称直接从学科实体对象中获取其名称属性subject.getName())并设置到节点信息中,方便在前端等展示时呈现相应的学科名称。
node.put("name", subject.getName()); node.put("name", subject.getName());
// 设置当前学科节点的父节点 ID这里设置为 "-1",表示它是顶层节点(没有父节点),符合树形结构中根节点的特征,当然具体的父节点标识规则可以根据业务实际情况调整。
node.put("parentId", "-1"); node.put("parentId", "-1");
// 将构建好的学科节点信息添加到 courseTree 中,这样就完成了一个学科节点在树形结构中的添加操作,后续还需要添加该学科下的课程节点作为其子节点。
courseTree.add(node); courseTree.add(node);
// 通过 CourseMapper 的 findBySubjectId 方法传入当前学科的唯一标识符subject.getUid()),从数据库中查询获取该学科下的课程列表信息,得到一个包含课程实体对象的列表,用于添加课程节点作为当前学科节点的子节点。
List<CourseEntity> courseList = courseMapper.findBySubjectId(subject.getUid()); List<CourseEntity> courseList = courseMapper.findBySubjectId(subject.getUid());
for(int k=0 ; k<courseList.size() ; k++){ // 遍历获取到的课程列表对于每个课程course同样要在树形结构中创建对应的节点信息并添加到 courseTree 中,作为当前学科节点的子节点。
for (int k = 0; k < courseList.size(); k++) {
CourseEntity course = courseList.get(k); CourseEntity course = courseList.get(k);
// 重新创建一个新的 JSONObject 对象,用于表示当前课程在树形结构中的节点信息,同样后续会设置其相关属性,如唯一标识符、名称、父节点 ID 等。
node = new JSONObject(); node = new JSONObject();
// 设置当前课程节点的唯一标识符直接使用课程实体对象的唯一标识符course.getUid()),使其在整个树形结构中有唯一标识,方便后续操作和查找等。
node.put("uid", course.getUid()); node.put("uid", course.getUid());
// 设置当前课程节点的名称从课程实体对象中获取其名称属性course.getName())并设置到节点信息中,以便在前端展示相应的课程名称。
node.put("name", course.getName()); node.put("name", course.getName());
// 设置当前课程节点的父节点 ID这里设置为 "s" 加上当前学科的唯一标识符subject.getUid()),表明该课程节点的父节点是对应的学科节点,建立起学科与课程之间的层级关联关系。
node.put("parentId", "s" + subject.getUid()); node.put("parentId", "s" + subject.getUid());
// 将构建好的课程节点信息添加到 courseTree 中,这样就完成了一个课程节点在树形结构中的添加操作,作为对应学科节点的子节点,不断重复这个过程,就构建出了完整的课程树形结构。
courseTree.add(node); courseTree.add(node);
} }
} }
return courseTree; return courseTree;
} }
/**
* getCourseCascaderTree
* Cascader使 JSONArray 便
*/
@Override @Override
public JSONArray getCourseCascaderTree() { public JSONArray getCourseCascaderTree() {
// 创建一个空的 JSONArray 对象,用于存储最终构建好的适合级联选择器的课程树形结构数据,后续会不断向其中添加节点信息来构建完整的树形结构。
JSONArray courseTree = new JSONArray(); JSONArray courseTree = new JSONArray();
// 通过 SubjectMapper 的 selectList 方法,传入 Condition.EMPTY表示使用默认的查询条件通常意味着查询所有符合条件的记录在这里就是所有学科记录
// 从数据库中获取所有的学科信息列表,得到一个包含所有学科实体对象的列表,用于后续构建树形结构时作为顶层节点(父节点)使用,与 getCourseTree 方法中的这一步类似,都是先获取学科基础信息。
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
List<SubjectEntity> subjectList = subjectMapper.selectList(Condition.EMPTY); List<SubjectEntity> subjectList = subjectMapper.selectList(Condition.EMPTY);
for(int i=0 ; i<subjectList.size() ; i++){
// 遍历获取到的学科列表对于每个学科subject都要在树形结构中创建对应的节点信息并添加到 courseTree 中,同时还要添加其下的课程节点信息作为子节点,整体构建思路与 getCourseTree 方法类似,但节点的属性设置等细节稍有不同,以符合级联选择器的数据格式要求。
for (int i = 0; i < subjectList.size(); i++) {
SubjectEntity subject = subjectList.get(i); SubjectEntity subject = subjectList.get(i);
// 创建一个新的 JSONObject 对象用于表示当前学科在树形结构中的节点信息后续会向其中设置各种属性如值value、标签label以及子节点children这些属性符合级联选择器对节点数据格式的要求。
JSONObject node = new JSONObject(); JSONObject node = new JSONObject();
// 设置当前学科节点的值value直接使用学科实体对象的唯一标识符subject.getUid()),这个值在级联选择器中用于唯一标识该节点,方便后续的选中、传递等操作,具体使用方式根据前端级联选择器的实现而定。
node.put("value", subject.getUid()); node.put("value", subject.getUid());
// 设置当前学科节点的标签label从学科实体对象中获取其名称属性subject.getName())并设置到节点信息中,这样在前端级联选择器中展示时会呈现相应的学科名称,方便用户识别和选择。
node.put("label", subject.getName()); node.put("label", subject.getName());
// 创建一个空的 JSONArray 对象,用于存储当前学科节点下的课程子节点信息,后续会遍历该学科下的课程列表,将课程节点信息添加到这个数组中,再设置到学科节点的 children 属性中,形成层级结构。
JSONArray children = new JSONArray(); JSONArray children = new JSONArray();
// 通过 CourseMapper 的 findBySubjectId 方法传入当前学科的唯一标识符subject.getUid()),从数据库中查询获取该学科下的课程列表信息,得到一个包含课程实体对象的列表,用于添加课程节点作为当前学科节点的子节点,与 getCourseTree 方法中的这一步操作相同,都是获取课程信息用于构建树形结构。
List<CourseEntity> courseList = courseMapper.findBySubjectId(subject.getUid()); List<CourseEntity> courseList = courseMapper.findBySubjectId(subject.getUid());
for(int k=0 ; k<courseList.size() ; k++){ // 遍历获取到的课程列表对于每个课程course同样要在树形结构中创建对应的节点信息并添加到 children 数组中,作为当前学科节点的子节点,构建出完整的学科 - 课程层级结构。
for (int k = 0; k < courseList.size(); k++) {
CourseEntity course = courseList.get(k); CourseEntity course = courseList.get(k);
// 创建一个新的 JSONObject 对象用于表示当前课程在树形结构中的节点信息同样后续会设置其相关属性如值value、标签label符合级联选择器对节点数据格式的要求。
JSONObject no = new JSONObject(); JSONObject no = new JSONObject();
// 设置当前课程节点的值value直接使用课程实体对象的唯一标识符course.getUid()),使其在整个树形结构中有唯一标识,方便在级联选择器中进行选择和传递等操作。
no.put("value", course.getUid()); no.put("value", course.getUid());
// 设置当前课程节点的标签label从课程实体对象中获取其名称属性course.getName())并设置到节点信息中,以便在前端级联选择器中展示相应的课程名称,方便用户识别和选择。
no.put("label", course.getName()); no.put("label", course.getName());
// 将构建好的课程节点信息添加到 children 数组中,这样就完成了一个课程节点在树形结构中的添加操作,作为对应学科节点的子节点,不断重复这个过程,就构建出了当前学科下包含课程子节点的层级结构。
children.add(no); children.add(no);
} }
// 将包含课程子节点信息的 children 数组设置到当前学科节点的 children 属性中,这样就完整地构建好了一个学科节点及其下课程子节点的树形结构信息,符合级联选择器对数据格式的要求。
node.put("children", children); node.put("children", children);
// 将构建好的学科节点信息添加到 courseTree 中,这样就完成了一个学科节点及其子节点在树形结构中的添加操作,不断重复这个过程,就构建出了整个适合级联选择器使用的课程树形结构。
courseTree.add(node); courseTree.add(node);
} }
return courseTree; return courseTree;
} }
} }

@ -4,111 +4,159 @@ import java.io.InterruptedIOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
// 定义一个抽象类,实现了 LogHandler 接口(这里假设 LogHandler 接口定义了一系列日志处理相关的方法,比如 info、error、debug 等,虽然在当前类中这些方法体暂时为空,但留给具体子类去实现具体的日志处理逻辑),
// 该抽象类主要用于处理运行时日志相关的通用逻辑,为具体的日志处理实现类提供基础框架和一些通用方法。
public abstract class AbstractRunningLogHandler implements LogHandler { public abstract class AbstractRunningLogHandler implements LogHandler {
private static Method getStackTraceMethod; // 通过反射获取的用于获取堆栈跟踪信息的方法对象,对应 Throwable 类中的 getStackTrace 方法,
private static Method getClassNameMethod; // 后续会利用这个方法来获取异常发生时的调用栈信息,以便确定日志相关的位置信息等,初始化为 null在静态代码块中进行初始化赋值。
private static Method getMethodNameMethod; private static Method getStackTraceMethod;
private static Method getFileNameMethod; // 通过反射获取的用于获取类名的方法对象,对应 java.lang.StackTraceElement 类中的 getClassName 方法,
private static Method getLineNumberMethod; // 用于从堆栈跟踪元素中提取类名信息,同样初始化为 null在静态代码块里完成初始化。
private static Method getClassNameMethod;
static { // 通过反射获取的用于获取方法名的方法对象,对应 java.lang.StackTraceElement 类中的 getMethodName 方法,
try { // 用来从堆栈跟踪元素里获取具体执行的方法名称,初始值为 null在静态块中初始化。
Class<?>[] noArgs = null; private static Method getMethodNameMethod;
getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs); // 通过反射获取的用于获取文件名的方法对象,对应 java.lang.StackTraceElement 类中的 getFileName 方法,
Class<?> stackTraceElementClass = Class.forName("java.lang.StackTraceElement"); // 可以从堆栈跟踪元素中获取对应的源文件名信息,初始设置为 null后续在静态代码块里赋值。
getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs); private static Method getFileNameMethod;
getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs); // 通过反射获取的用于获取行号的方法对象,对应 java.lang.StackTraceElement 类中的 getLineNumber 方法,
getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs); // 目的是从堆栈跟踪元素中获取代码执行到的具体行号信息,开始时为 null在静态代码块中进行初始化操作。
getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs); private static Method getLineNumberMethod;
} catch (ClassNotFoundException ex) {
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
} catch (NoSuchMethodException ex) {
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
}
}
/**
* classStackTraceElement
*
* @param t
* @param fqnOfCallingClass
*
* @return
*/
protected StackTraceElement getRunningStackTrace(Throwable t, String fqnOfCallingClass) {
if (getLineNumberMethod != null) {
try {
Object[] noArgs = null;
Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs);
for (int i = elements.length - 1; i >= 0; i--) {
String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
if (fqnOfCallingClass.equals(thisClass)) {
// 执行class名称
String className = fqnOfCallingClass;
// 执行方法名称
String methodName = (String) getMethodNameMethod.invoke(elements[i], noArgs);
// 执行class文件名称
String fileName = (String) getFileNameMethod.invoke(elements[i], noArgs);
// 执行到行号
int lineNumber = ((Integer) getLineNumberMethod.invoke(elements[i], noArgs)).intValue();
return new StackTraceElement(className, methodName, fileName, lineNumber);
}
}
} catch (IllegalAccessException ex) {
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (InvocationTargetException ex) {
if (ex.getTargetException() instanceof InterruptedException
|| ex.getTargetException() instanceof InterruptedIOException) {
Thread.currentThread().interrupt();
}
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (RuntimeException ex) {
LogDebug.debug("failed using JDK 1.4 methods", ex);
}
}
return this.createDefaultStackTrace();
}
/**
* StackTraceElement
*
* @return
*/
private StackTraceElement createDefaultStackTrace() {
return new StackTraceElement(this.getClass().getName(), "log", this.getClass().getName(), 0);
}
@Override
public void info(String msg, String fqnOfCallingClass) {
}
@Override
public void info(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void error(String msg, String fqnOfCallingClass) {
}
@Override
public void error(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, Throwable t, String fqnOfCallingClass) {
}
// 静态代码块,在类加载时执行,主要用于通过反射机制获取一些后续用于处理堆栈跟踪信息相关的方法对象,
// 如果获取过程中出现异常(比如类不存在或者方法不存在等情况),会打印相应的提示信息,表示将使用 JDK 1.4 之前的方式来确定位置信息(虽然这里并没有展示具体是什么替代方式,可能由具体子类去处理或者在其他相关逻辑中体现)。
static {
try {
// 创建一个空的 Class<?> 数组,表示无参数的情况,用于后续获取方法时指定参数类型,因为这里要获取的几个方法都是无参数的方法。
Class<?>[] noArgs = null;
// 通过反射获取 Throwable 类的 getStackTrace 方法对象,该方法用于获取异常的堆栈跟踪信息,后续可以基于这个信息进一步提取类、方法、文件、行号等详细信息,
getStackTraceMethod = Throwable.class.getMethod("getStackTrace", noArgs);
// 通过反射获取 java.lang.StackTraceElement 类的 Class 对象,因为后续要获取这个类中的几个方法对象,所以先获取其 Class 类型。
Class<?> stackTraceElementClass = Class.forName("java.lang.StackTraceElement");
// 通过反射获取 StackTraceElement 类的 getClassName 方法对象,用于从堆栈跟踪元素中获取类名信息。
getClassNameMethod = stackTraceElementClass.getMethod("getClassName", noArgs);
// 通过反射获取 StackTraceElement 类的 getMethodName 方法对象,以便从堆栈跟踪元素中获取方法名信息。
getMethodNameMethod = stackTraceElementClass.getMethod("getMethodName", noArgs);
// 通过反射获取 StackTraceElement 类的 getFileName 方法对象,用于从堆栈跟踪元素中获取文件名信息。
getFileNameMethod = stackTraceElementClass.getMethod("getFileName", noArgs);
// 通过反射获取 StackTraceElement 类的 getLineNumber 方法对象,使得可以从堆栈跟踪元素中获取行号信息。
getLineNumberMethod = stackTraceElementClass.getMethod("getLineNumber", noArgs);
} catch (ClassNotFoundException ex) {
// 如果出现 ClassNotFoundException 异常,意味着找不到对应的类(比如这里的 StackTraceElement 类在某些特殊环境下不存在或者加载失败),
// 则打印提示信息,表示将使用 JDK 1.4 之前的方法来确定位置信息,这里只是简单打印提示,具体的替代逻辑可能在其他地方或者由子类去实现。
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
} catch (NoSuchMethodException ex) {
// 如果出现 NoSuchMethodException 异常,说明要获取的某个方法(比如上面获取的几个 StackTraceElement 类中的方法)不存在,
// 同样打印提示信息,表示将使用 JDK 1.4 之前的方法来确定位置信息,后续具体怎么处理依赖于具体的业务逻辑或者子类实现。
LogDebug.debug("will use pre-JDK 1.4 methods to determine location.");
}
}
/**
* classStackTraceElement
* tfqnOfCallingClassStackTraceElement
* StackTraceElement
* 访 StackTraceElement
*
* @param t
* @param fqnOfCallingClass
*
* @return StackTraceElement StackTraceElement
*/
protected StackTraceElement getRunningStackTrace(Throwable t, String fqnOfCallingClass) {
if (getLineNumberMethod!= null) {
try {
// 创建一个空的 Object 数组,表示无参数的情况,用于后续通过反射调用方法时作为参数传递,因为这里要调用的几个方法都是无参数的方法。
Object[] noArgs = null;
// 通过反射调用异常对象t的 getStackTrace 方法,获取其堆栈跟踪信息,返回的是一个 Object 数组(实际上是 StackTraceElement 数组,但通过反射获取时以 Object 数组形式返回),
Object[] elements = (Object[]) getStackTraceMethod.invoke(t, noArgs);
// 从后往前遍历堆栈跟踪元素数组,因为通常最新的调用信息在数组的末尾,通过倒序遍历可以更快地找到与指定调用类相关的元素。
for (int i = elements.length - 1; i >= 0; i--) {
// 通过反射调用当前堆栈跟踪元素elements[i])的 getClassName 方法,获取其类名信息,并转换为 String 类型,用于与传入的调用类全限定名进行比对。
String thisClass = (String) getClassNameMethod.invoke(elements[i], noArgs);
if (fqnOfCallingClass.equals(thisClass)) {
// 如果找到了与传入的调用类全限定名匹配的堆栈跟踪元素,则获取其相关信息来构建一个 StackTraceElement 对象并返回。
// 获取类名这里直接使用传入的调用类全限定名fqnOfCallingClass作为类名也可以根据实际情况进行进一步处理比如提取简单类名等不过这里直接使用了传入的全限定名。
String className = fqnOfCallingClass;
// 通过反射调用当前堆栈跟踪元素elements[i])的 getMethodName 方法,获取其方法名信息,并转换为 String 类型,用于设置到要返回的 StackTraceElement 对象中。
String methodName = (String) getMethodNameMethod.invoke(elements[i], noArgs);
// 通过反射调用当前堆栈跟踪元素elements[i])的 getFileName 方法,获取其文件名信息,并转换为 String 类型,用于设置到要返回的 StackTraceElement 对象中。
String fileName = (String) getFileNameMethod.invoke(elements[i], noArgs);
// 通过反射调用当前堆栈跟踪元素elements[i])的 getLineNumber 方法,获取其行号信息,先转换为 Integer 类型,再获取其 int 值,用于设置到要返回的 StackTraceElement 对象中。
int lineNumber = ((Integer) getLineNumberMethod.invoke(elements[i], noArgs)).intValue();
return new StackTraceElement(className, methodName, fileName, lineNumber);
}
}
} catch (IllegalAccessException ex) {
// 如果出现 IllegalAccessException 异常,说明在通过反射调用方法时发生了非法访问的情况(比如方法不可访问等原因),
// 则打印调试信息,表示使用 JDK 1.4 相关方法失败,并将异常信息传递进去,方便后续排查问题,不过这里只是简单打印调试信息,具体的处理逻辑可能可以进一步完善。
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (InvocationTargetException ex) {
// 如果出现 InvocationTargetException 异常,说明在通过反射调用方法时,被调用方法内部抛出了异常,需要进一步判断这个内部抛出的异常类型。
if (ex.getTargetException() instanceof InterruptedException
|| ex.getTargetException() instanceof InterruptedIOException) {
// 如果内部抛出的异常是 InterruptedException 或者 InterruptedIOException表示线程被中断相关的情况
// 则重新设置当前线程的中断状态,以便让调用者知道线程被中断了,后续可以根据这个中断状态进行相应的处理,符合 Java 中处理线程中断的规范。
Thread.currentThread().interrupt();
}
// 无论内部抛出的异常具体是什么类型(除了上面特殊处理的中断相关异常外),都打印调试信息,表示使用 JDK 1.4 相关方法失败,并将异常信息传递进去,方便后续排查问题。
LogDebug.debug("failed using JDK 1.4 methods", ex);
} catch (RuntimeException ex) {
// 如果出现 RuntimeException 异常,同样打印调试信息,表示使用 JDK 1.4 相关方法失败,并将异常信息传递进去,方便后续排查问题,
// 这里统一处理了各种运行时异常情况,保证方法在出现异常时能进行一定的错误提示,避免程序因为未处理的异常而崩溃。
LogDebug.debug("failed using JDK 1.4 methods", ex);
}
}
// 如果在获取指定的 StackTraceElement 过程中出现了各种问题(比如反射方法获取失败或者在提取信息时出现异常等情况),则调用 createDefaultStackTrace 方法创建并返回一个默认的 StackTraceElement 对象。
return this.createDefaultStackTrace();
}
/**
* StackTraceElement
* StackTraceElement 使
* StackTraceElement this.getClass().getName() "log"this.getClass().getName() 0
* 使
*
* @return StackTraceElement
*/
private StackTraceElement createDefaultStackTrace() {
return new StackTraceElement(this.getClass().getName(), "log", this.getClass().getName(), 0);
}
// 以下是实现 LogHandler 接口的各个日志处理方法,在当前抽象类中这些方法体为空,因为具体的日志处理逻辑(比如将日志信息输出到哪里、如何格式化等)需要由具体的子类根据不同的业务需求去实现,
// 这里只是定义了方法签名,遵循了接口的规范,保证了抽象类作为日志处理的基础框架,子类可以针对性地重写这些方法来完成实际的日志处理操作。
@Override
public void info(String msg, String fqnOfCallingClass) {
}
@Override
public void info(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void error(String msg, String fqnOfCallingClass) {
}
@Override
public void error(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, String fqnOfCallingClass) {
}
@Override
public void debug(String msg, Throwable t, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, String fqnOfCallingClass) {
}
@Override
public void warning(String msg, Throwable t, String fqnOfCallingClass) {
}
} }

@ -5,51 +5,86 @@ import java.text.SimpleDateFormat;
import java.util.Calendar; import java.util.Calendar;
import java.util.Date; import java.util.Date;
// 这个类提供了一系列与日期处理相关的实用方法,用于日期的格式化、解析、日期差值计算以及日期增减等操作,方便在项目中对日期进行各种常见的处理。
public class DateUtils { public class DateUtils {
// 定义默认的日期格式字符串,格式为 "yyyy-MM-dd",用于在一些方法中作为默认的日期格式化样式,方便统一处理日期的显示格式。
public static final String DEFAULT_PATTERN = "yyyy-MM-dd"; public static final String DEFAULT_PATTERN = "yyyy-MM-dd";
/**
*
* daypattern
* 使DEFAULT_PATTERN
*
* @param day
* @param pattern "yyyy-MM-dd HH:mm:ss"
* @return
*/
public static String getOneDayFromNow(int day, String pattern) { public static String getOneDayFromNow(int day, String pattern) {
// 获取一个 Calendar 实例,它用于对日期进行各种操作,比如日期的增减、获取日期的各个字段等,这里获取的是当前系统时间对应的 Calendar 实例。
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
// 将 Calendar 的时间设置为当前日期(通过传入一个 Date 对象表示当前时间,这里直接使用 new Date() 获取当前时间),以便后续在此基础上进行日期偏移操作。
cal.setTime(new Date()); cal.setTime(new Date());
// 在当前日期基础上增加或减少指定的天数,通过调用 add 方法并指定 Calendar.DAY_OF_MONTH 字段以及要偏移的天数day来实现日期的偏移操作。
cal.add(Calendar.DAY_OF_MONTH, day); cal.add(Calendar.DAY_OF_MONTH, day);
// 创建一个 SimpleDateFormat 对象用于按照指定的格式pattern对日期进行格式化操作将日期转换为字符串形式。
SimpleDateFormat sdf = new SimpleDateFormat(pattern); SimpleDateFormat sdf = new SimpleDateFormat(pattern);
// 使用 SimpleDateFormat 的 format 方法将偏移后的日期cal.getTime() 返回的是一个 Date 对象,表示偏移后的日期时间)格式化为字符串,并返回该格式化后的日期字符串。
return sdf.format(cal.getTime()); return sdf.format(cal.getTime());
} }
/**
* 使
* getOneDayFromNow day getOneDayFromNow 使DEFAULT_PATTERN
*
* @param day
* @return "yyyy-MM-dd"
*/
public static String getOneDayFromNow(int day) { public static String getOneDayFromNow(int day) {
return DateUtils.getOneDayFromNow(day, DEFAULT_PATTERN); return DateUtils.getOneDayFromNow(day, DEFAULT_PATTERN);
} }
/** /**
* *
* "yyyy-MM-dd" Date
* smdate bdate
* *
* @param smdate * @param smdate Date
* * @param bdate Date
* @param bdate * @return
* * @throws ParseException
* @return
* @throws ParseException
* @throws java.text.ParseException
*/ */
public static int daysBetween(Date smdate, Date bdate) public static int daysBetween(Date smdate, Date bdate) throws ParseException {
throws ParseException { // 创建一个 SimpleDateFormat 对象,指定日期格式为 "yyyy-MM-dd",用于统一将传入的日期格式化为该格式后再进行后续的时间戳计算等操作,确保日期格式的一致性。
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 先将传入的较小日期smdate按照指定格式进行格式化再解析为 Date 对象,这样做的目的是去除日期中的时间部分(如果有的话),只保留日期部分,保证计算天数的准确性。
smdate = sdf.parse(sdf.format(smdate)); smdate = sdf.parse(sdf.format(smdate));
// 同样地对较大日期bdate进行格式化和解析操作去除时间部分只保留日期部分以便后续基于日期进行天数差值的计算。
bdate = sdf.parse(sdf.format(bdate)); bdate = sdf.parse(sdf.format(bdate));
// 获取一个 Calendar 实例用于获取日期对应的时间戳等操作这里以较小日期smdate为基础进行设置。
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
cal.setTime(smdate); cal.setTime(smdate);
// 获取较小日期对应的时间戳(以毫秒为单位),通过 Calendar 的getTimeInMillis 方法获取,表示从 1970 年 1 月 1 日 00:00:00 UTC 到该日期时间的毫秒数。
long time1 = cal.getTimeInMillis(); long time1 = cal.getTimeInMillis();
// 重新设置 Calendar 的时间为较大日期bdate以便获取其对应的时间戳。
cal.setTime(bdate); cal.setTime(bdate);
// 获取较大日期对应的时间戳(以毫秒为单位)。
long time2 = cal.getTimeInMillis(); long time2 = cal.getTimeInMillis();
// 计算两个日期时间戳的差值以毫秒为单位然后除以一天对应的毫秒数1000 * 3600 * 24即 1000 毫秒/秒 * 3600 秒/小时 * 24 小时/天),得到相差的天数,这里差值是一个 long 类型。
long between_days = (time2 - time1) / (1000 * 3600 * 24); long between_days = (time2 - time1) / (1000 * 3600 * 24);
// 将计算得到的相差天数long 类型)转换为整数类型并返回,通过将 long 值转换为 String 再解析为 Integer 的方式实现,虽然这种方式略显繁琐,但能确保类型转换的正确性。
return Integer.parseInt(String.valueOf(between_days)); return Integer.parseInt(String.valueOf(between_days));
} }
// main 方法,用于简单测试 daysBetween 方法,不过这里传入的参数都是 null实际运行时会抛出 NullPointerException
// 正常使用时应该传入有效的 Date 对象参数进行测试或者在其他合适的地方调用 daysBetween 方法并传入正确的日期参数来计算日期差值。
public static void main(String[] args) throws ParseException { public static void main(String[] args) throws ParseException {
System.out.println(daysBetween(null, null)); System.out.println(daysBetween(null, null));
} }
// 定义一系列日期格式的常量字符串,方便在不同的日期格式化场景中直接使用,涵盖了英文简写、英文全称、精确到毫秒的完整时间以及对应的中文简写、中文全称、精确到毫秒的完整中文时间等多种格式。
/** /**
* 2010-12-01 * 2010-12-01
*/ */
@ -77,6 +112,7 @@ public class DateUtils {
/** /**
* date pattern * date pattern
* FORMAT_LONG "yyyy-MM-dd HH:mm:ss"使
*/ */
public static String getDatePattern() { public static String getDatePattern() {
return FORMAT_LONG; return FORMAT_LONG;
@ -84,8 +120,9 @@ public class DateUtils {
/** /**
* *
* 使 getDatePattern "yyyy-MM-dd HH:mm:ss"new Date()
* *
* @return * @return
*/ */
public static String getNow() { public static String getNow() {
return format(new Date()); return format(new Date());
@ -93,9 +130,10 @@ public class DateUtils {
/** /**
* *
* formatnew Date() 便
* *
* @param format * @param format "yyyy-MM-dd""yyyy年MM月dd日"
* @return * @return
*/ */
public static String getNow(String format) { public static String getNow(String format) {
return format(new Date(), format); return format(new Date(), format);
@ -103,9 +141,11 @@ public class DateUtils {
/** /**
* 使 * 使
* 使 getDatePattern "yyyy-MM-dd HH:mm:ss"date
* null
* *
* @param date * @param date Date null
* @return * @return date null
*/ */
public static String format(Date date) { public static String format(Date date) {
return format(date, getDatePattern()); return format(date, getDatePattern());
@ -113,17 +153,19 @@ public class DateUtils {
/** /**
* 使 * 使
* patterndate
* null
* *
* @param date * @param date Date null
* * @param pattern
* @param pattern * @return date null
*
* @return
*/ */
public static String format(Date date, String pattern) { public static String format(Date date, String pattern) {
String returnValue = ""; String returnValue = "";
if (date != null) { if (date!= null) {
// 创建一个 SimpleDateFormat 对象按照用户指定的格式pattern进行初始化用于对日期进行格式化操作。
SimpleDateFormat df = new SimpleDateFormat(pattern); SimpleDateFormat df = new SimpleDateFormat(pattern);
// 使用 SimpleDateFormat 的 format 方法将日期date格式化为字符串并赋值给 returnValue以便后续返回。
returnValue = df.format(date); returnValue = df.format(date);
} }
return (returnValue); return (returnValue);
@ -131,12 +173,12 @@ public class DateUtils {
/** /**
* 使 * 使
* timestamp long Date
* pattern便
* *
* @param timestamp * @param timestamp 1970 1 1 00:00:00 UTC "1612345678901"
* * @param pattern
* @param pattern * @return
*
* @return
*/ */
public static String format(String timestamp, String pattern) { public static String format(String timestamp, String pattern) {
SimpleDateFormat sdf = new SimpleDateFormat(pattern); SimpleDateFormat sdf = new SimpleDateFormat(pattern);
@ -145,10 +187,11 @@ public class DateUtils {
/** /**
* 使 * 使
* strDate getDatePattern "yyyy-MM-dd HH:mm:ss"
* Date ParseException null
* *
* @param strDate * @param strDate
* * @return Date null
* @return
*/ */
public static Date parse(String strDate) { public static Date parse(String strDate) {
return parse(strDate, getDatePattern()); return parse(strDate, getDatePattern());
@ -156,12 +199,12 @@ public class DateUtils {
/** /**
* 使 * 使
* patternstrDate Date
* ParseException null
* *
* @param strDate * @param strDate
* * @param pattern
* @param pattern * @return Date null
*
* @return
*/ */
public static Date parse(String strDate, String pattern) { public static Date parse(String strDate, String pattern) {
SimpleDateFormat df = new SimpleDateFormat(pattern); SimpleDateFormat df = new SimpleDateFormat(pattern);
@ -175,12 +218,11 @@ public class DateUtils {
/** /**
* *
* Calendar daten便
* *
* @param date * @param date Date
* * @param n
* @param n * @return
*
* @return
*/ */
public static Date addMonth(Date date, int n) { public static Date addMonth(Date date, int n) {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
@ -191,12 +233,11 @@ public class DateUtils {
/** /**
* *
* Calendar daten
* *
* @param date * @param date Date
* * @param n
* @param n * @return
*
* @return
*/ */
public static Date addDay(Date date, int n) { public static Date addDay(Date date, int n) {
Calendar cal = Calendar.getInstance(); Calendar cal = Calendar.getInstance();
@ -207,6 +248,8 @@ public class DateUtils {
/** /**
* *
* 使FORMAT_FULL "yyyy-MM-dd HH:mm:ss.S" Calendar.getInstance Calendar Date
* 便
*/ */
public static String getTimeString() { public static String getTimeString() {
SimpleDateFormat df = new SimpleDateFormat(FORMAT_FULL); SimpleDateFormat df = new SimpleDateFormat(FORMAT_FULL);
@ -216,59 +259,17 @@ public class DateUtils {
/** /**
* *
* date format 使 4
* 4
* *
* @param date * @param date Date
* * @return "2024"
* @return
*/ */
public static String getYear(Date date) { public static String getYear(Date date) {
return format(date).substring(0, 4); return format(date).substring(0, 4);
} }
/** /**
* *
* * date
* @param date *
*
* @return
*/
public static int countDays(String date) {
long t = Calendar.getInstance().getTime().getTime();
Calendar c = Calendar.getInstance();
c.setTime(parse(date));
long t1 = c.getTime().getTime();
return (int) (t / 1000 - t1 / 1000) / 3600 / 24;
}
/**
*
*
* @param date
*
* @param format
*
* @return
*/
public static int countDays(String date, String format) {
long t = Calendar.getInstance().getTime().getTime();
Calendar c = Calendar.getInstance();
c.setTime(parse(date, format));
long t1 = c.getTime().getTime();
return (int) (t / 1000 - t1 / 1000) / 3600 / 24;
}
public static String timeFormat(Date date, String format, Boolean flag, int beforeDay, int nowDay) {
if(date == null) {
date = new Date();
}
Calendar cal = Calendar.getInstance();
cal.setTime(date);
if(flag) {
cal.add(Calendar.DATE,-30);
} else {
cal.add(Calendar.DATE,0);
}
SimpleDateFormat sdf = new SimpleDateFormat(format);
return sdf.format(cal.getTime());
}
}

@ -1,56 +1,86 @@
package com.tamguo.util; package com.tamguo.util;
public class IdGen // IdGen类用于生成唯一标识符ID很可能基于某种分布式唯一ID生成算法从代码结构推测可能类似雪花算法的思路
{ // 它可以根据配置的参数如工作节点ID、数据中心ID等以及一些规则来生成具有唯一性的ID并且支持添加自定义后缀以及指定后缀位置等功能。
public class IdGen {
// 工作节点ID用于在分布式环境中区分不同的工作节点不同的工作节点生成的ID应该具有唯一性通过位运算等规则来限定其取值范围。
private long workerId; private long workerId;
// 数据中心ID用于区分不同的数据中心同样在分布式系统架构里参与ID生成的唯一性保证也有相应的取值范围限制。
private long datacenterId; private long datacenterId;
// 序列号用于在同一毫秒内对生成的ID进行区分在同一时间同一毫秒不同的序列号保证生成的ID各不相同初始值为0。
private long sequence = 0L; private long sequence = 0L;
// 起始时间戳单位毫秒通常是一个固定的历史时间点作为ID生成算法中的时间基线用于计算时间戳差值等操作以保证生成的ID在时间维度上的唯一性和有序性。
private long twepoch = 1288834974657L; private long twepoch = 1288834974657L;
// 用于表示 workerId 所占的位数,通过这些位数设定来确定 workerId 的取值范围当前设置为5位。
private long workerIdBits = 5L; private long workerIdBits = 5L;
// 表示 datacenterId 所占的位数,以此限定 datacenterId 的取值范围同样设置为5位。
private long datacenterIdBits = 5L; private long datacenterIdBits = 5L;
// 根据 workerIdBits 计算出的 workerId 的最大允许值,通过位运算得出,确保 workerId 在合理的取值范围内避免超出导致ID生成出现问题。
private long maxWorkerId = -1L ^ (-1L << workerIdBits); private long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 依据 datacenterIdBits 计算得到的 datacenterId 的最大取值,通过位运算来限定其范围,保证 datacenterId 的合法性。
private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 序列号所占的位数决定了在同一毫秒内可生成的不同ID的数量这里设置为12位。
private long sequenceBits = 12L; private long sequenceBits = 12L;
// 在生成ID时用于将 sequence序列号左移的位数其值等于 sequenceBits用于在ID的二进制表示中为 sequence 预留合适的位置。
private long workerIdShift = sequenceBits; private long workerIdShift = sequenceBits;
// 在生成ID时用于将 datacenterId 左移的位数,计算方式是 sequenceBits 与 workerIdBits 之和,确定 datacenterId 在ID二进制表示中的位置。
private long datacenterIdShift = sequenceBits + workerIdBits; private long datacenterIdShift = sequenceBits + workerIdBits;
// 在生成ID时用于将时间戳timestamp左移的位数通过 sequenceBits、workerIdBits 和 datacenterIdBits 三者相加得出为时间戳在ID二进制表示中预留正确的位置。
private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
// 序列号的掩码,通过位运算生成,用于在同一毫秒内对 sequence 进行循环计数时的边界判断,保证序列号在规定的位数范围内循环使用。
private long sequenceMask = -1L ^ (-1L << sequenceBits); private long sequenceMask = -1L ^ (-1L << sequenceBits);
// 记录上一次生成ID时的时间戳单位毫秒用于和当前时间戳对比判断是否在同一毫秒内以及处理时间回拨等情况初始值为 -1L。
private long lastTimestamp = -1L; private long lastTimestamp = -1L;
// 自定义的后缀字符串可用于给生成的ID添加额外的标识信息方便在业务中根据后缀进行分类、识别等操作初始值为 null。
private String suffix; private String suffix;
// 布尔值用于指示后缀添加的位置true 表示添加在ID的前缀位置false 表示添加在ID的后缀位置初始值为 false。
private boolean flag; private boolean flag;
// 内部静态类,用于实现单例模式,保证整个应用程序中只有一个 IdGen 实例(如果使用默认无参构造函数获取实例的情况),
// 通过类加载机制保证线程安全地创建和获取这个唯一实例避免多次实例化造成的资源浪费以及保证ID生成规则的一致性。
private static class IdGenHolder { private static class IdGenHolder {
// 创建并持有一个静态的最终 IdGen 实例,在类加载时就会初始化这个实例,并且只会初始化一次,后续通过 get 方法获取的都是这个唯一实例。
private static final IdGen instance = new IdGen(); private static final IdGen instance = new IdGen();
} }
public static IdGen get(){ // 静态方法,用于获取单例的 IdGen 实例使用默认配置的情况外部类可以通过这个方法方便地获取到用于生成ID的唯一实例遵循单例模式的获取实例方式。
public static IdGen get() {
return IdGenHolder.instance; return IdGenHolder.instance;
} }
/** /**
* id * id
* @param suffix * IdGen suffixflagID
* @param flag true: false: * null 0 IdGen ID
* @return *
* @param suffix ID使ID
* @param flag trueIDfalseIDID
* @return IdGen
*/ */
public static IdGen get(String suffix,boolean flag){ public static IdGen get(String suffix, boolean flag) {
if(suffix == null || suffix.trim().length() == 0) if (suffix == null || suffix.trim().length() == 0)
return IdGenHolder.instance; return IdGenHolder.instance;
else{ else {
return new IdGen(suffix,flag); return new IdGen(suffix, flag);
} }
} }
// 默认的无参构造函数调用另一个带有两个参数均为0L的构造函数来初始化对象给 workerId 和 datacenterId 都赋予初始值0适用于一些简单场景或者后续有默认配置的情况。
public IdGen() { public IdGen() {
this(0L, 0L); this(0L, 0L);
} }
public IdGen(String suffix,boolean flag){ // 构造函数,用于创建一个带有自定义后缀和后缀位置标志的 IdGen 实例接收后缀字符串suffix和后缀位置标志flag作为参数
// 将传入的参数赋值给相应的成员变量以便后续在生成ID时根据这些配置来添加后缀。
public IdGen(String suffix, boolean flag) {
this.suffix = suffix; this.suffix = suffix;
this.flag = flag; this.flag = flag;
} }
// 构造函数,用于创建一个 IdGen 实例并传入工作节点IDworkerId和数据中心IDdatacenterId参数
// 在创建实例时会对传入的 workerId 和 datacenterId 进行合法性检查确保它们在预先设定的取值范围内通过与最大允许值比较以及判断是否小于0来验证
// 如果超出范围则抛出 IllegalArgumentException 异常保证ID生成参数的有效性避免生成不符合规则的ID。
public IdGen(long workerId, long datacenterId) { public IdGen(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) { if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
@ -62,28 +92,57 @@ public class IdGen
this.datacenterId = datacenterId; this.datacenterId = datacenterId;
} }
/**
* ID
* IDIDIDID
* IDID
*
* @return ID使
*/
public synchronized String nextId() { public synchronized String nextId() {
// 获取当前时间戳(单位:毫秒),通过调用 timeGen 方法来获取这个时间戳是生成ID的重要依据之一用于区分不同时间生成的ID保证时间维度上的唯一性。
long timestamp = timeGen(); long timestamp = timeGen();
// 判断当前时间戳是否小于上一次生成ID的时间戳lastTimestamp如果小于则说明出现了时间回拨的情况比如系统时间被手动调整回退了
// 这种情况下为了保证ID的唯一性和时间顺序性会抛出 RuntimeException 异常拒绝生成ID因为时间回拨可能导致ID重复等问题。
if (timestamp < lastTimestamp) { if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format( throw new RuntimeException(String.format(
"Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); "Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
} }
// 如果当前时间戳与上一次生成ID的时间戳相等说明在同一毫秒内需要对序列号sequence进行处理通过自增并与 sequenceMask 进行按位与操作,
// 实现序列号在规定的位数范围内循环递增保证在同一毫秒内不同调用生成不同的ID如果序列号达到最大值sequenceMask 对应的最大值即循环一轮回到0
// 则需要等待到下一个毫秒再生成ID通过调用 tilNextMillis 方法来获取下一个可用的时间戳。
if (lastTimestamp == timestamp) { if (lastTimestamp == timestamp) {
sequence = (sequence + 1) & sequenceMask; sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) { if (sequence == 0) {
timestamp = tilNextMillis(lastTimestamp); timestamp = tilNextMillis(lastTimestamp);
} }
} else { } else {
// 如果当前时间戳与上一次的不同说明进入了新的一毫秒此时将序列号重置为0重新开始在这一毫秒内的ID生成计数。
sequence = 0L; sequence = 0L;
} }
// 更新 lastTimestamp 为当前的时间戳为下一次生成ID时判断时间戳情况做准备确保每次生成ID都能正确处理时间相关的逻辑。
lastTimestamp = timestamp; lastTimestamp = timestamp;
// 通过位运算组合时间戳、数据中心ID、工作节点ID和序列号等信息构建出一个唯一的长整型数值serialNumber这个数值就是ID的核心部分
// 其位运算的顺序和位移的位数都是根据前面定义的各个参数(如 timestampLeftShift、datacenterIdShift、workerIdShift 等来确定的保证了各个部分在ID中的正确位置和唯一性体现。
long serialNumber = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) long serialNumber = ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence; | (workerId << workerIdShift) | sequence;
return (suffix == null || suffix.trim().length() == 0) ? serialNumber+"" : (flag ? (new StringBuffer()).append(suffix).append(serialNumber).toString() : (new StringBuffer()).append(serialNumber).append(suffix).toString()); // 根据是否有自定义后缀suffix以及后缀添加位置标志flag来决定最终返回的ID字符串形式
// 如果后缀为空或者去除空格后长度为0则直接将 serialNumber 转换为字符串返回;
// 如果 flag 为 true表示后缀添加在前面则通过 StringBuffer 先拼接后缀再拼接 serialNumber 并转换为字符串返回;
// 如果 flag 为 false表示后缀添加在后面则通过 StringBuffer 先拼接 serialNumber 再拼接后缀并转换为字符串返回从而生成符合要求的带有后缀或不带后缀的唯一ID字符串。
return (suffix == null || suffix.trim().length() == 0)? serialNumber + "" : (flag? (new StringBuffer()).append(suffix).append(serialNumber).toString() : (new StringBuffer()).append(serialNumber).append(suffix).toString());
} }
/**
*
* IDID
* timeGen lastTimestamp lastTimestamp
*
* @param lastTimestamp IDID
* @return lastTimestamp IDID
*/
protected long tilNextMillis(long lastTimestamp) { protected long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen(); long timestamp = timeGen();
while (timestamp <= lastTimestamp) { while (timestamp <= lastTimestamp) {
@ -92,8 +151,14 @@ public class IdGen
return timestamp; return timestamp;
} }
/**
*
* System.currentTimeMillis ID
*
*
* @return 19701100:00:00 UTCID
*/
protected long timeGen() { protected long timeGen() {
return System.currentTimeMillis(); return System.currentTimeMillis();
} }
} }

@ -6,9 +6,21 @@ import java.io.IOException;
import java.io.ObjectInputStream; import java.io.ObjectInputStream;
import java.io.ObjectOutputStream; import java.io.ObjectOutputStream;
// ObjectUtil类继承自SerializeTranscoder这里假设SerializeTranscoder是一个与序列化、反序列化相关的抽象类或者接口定义了一些序列化和反序列化相关的方法签名
// 主要提供了对象序列化、反序列化以及对象相等性比较的实用功能,方便在需要处理对象的持久化存储、网络传输以及对象比较等场景中使用。
public class ObjectUtil extends SerializeTranscoder { public class ObjectUtil extends SerializeTranscoder {
/**
*
* value null NullPointerException
* null I/O IllegalArgumentException 便
*
* @param value null Serializable Java
* @return 便
*/
@Override @Override
public byte[] serialize(Object value) { public byte[] serialize(Object value) {
// 首先检查传入的对象是否为 null如果是则抛出异常提示不能序列化 null 对象。
if (value == null) { if (value == null) {
throw new NullPointerException("Can't serialize null"); throw new NullPointerException("Can't serialize null");
} }
@ -16,54 +28,98 @@ public class ObjectUtil extends SerializeTranscoder {
ByteArrayOutputStream bos = null; ByteArrayOutputStream bos = null;
ObjectOutputStream os = null; ObjectOutputStream os = null;
try { try {
// 创建一个 ByteArrayOutputStream 对象,它用于在内存中缓冲要序列化的数据,后续可以方便地转换为字节数组获取序列化后的内容。
bos = new ByteArrayOutputStream(); bos = new ByteArrayOutputStream();
// 创建一个 ObjectOutputStream 对象,它用于将对象转换为字节流进行序列化,将其关联到 ByteArrayOutputStream这样写入 ObjectOutputStream 的数据最终会存储到 ByteArrayOutputStream 中。
os = new ObjectOutputStream(bos); os = new ObjectOutputStream(bos);
// 通过 ObjectOutputStream 的 writeObject 方法将传入的对象value进行序列化并写入到流中开始实际的序列化操作将对象的状态信息转换为字节形式。
os.writeObject(value); os.writeObject(value);
// 关闭 ObjectOutputStream释放相关资源注意关闭流时可能会抛出 IOException这里需要进行异常处理不过在 finally 块中还会进一步确保流的关闭操作。
os.close(); os.close();
// 关闭 ByteArrayOutputStream同样是释放资源也可能抛出 IOException同样在 finally 块中再次保障其能正确关闭。
bos.close(); bos.close();
// 将 ByteArrayOutputStream 中的字节数据获取出来,赋值给 result 变量,这个就是最终序列化后的字节数组,代表了传入对象序列化后的结果。
result = bos.toByteArray(); result = bos.toByteArray();
} catch (IOException e) { } catch (IOException e) {
// 如果在序列化过程中出现 I/O 异常(比如流写入失败、关闭流出错等情况),则抛出 IllegalArgumentException 异常,并将原始的 IOException 作为原因传递进去,
// 提示出现了不可序列化的对象问题,同时方便调用者获取详细的异常信息来排查到底是哪里的序列化操作出现了错误。
throw new IllegalArgumentException("Non-serializable object", e); throw new IllegalArgumentException("Non-serializable object", e);
} finally { } finally {
// 调用 close 方法(这里假设是类中定义的用于关闭流的辅助方法,确保流能正确关闭并释放资源,避免资源泄漏)关闭 ObjectOutputStream无论前面是否出现异常都要保证流能正确关闭。
close(os); close(os);
// 同样调用 close 方法关闭 ByteArrayOutputStream保障资源的正确释放。
close(bos); close(bos);
} }
return result; return result;
} }
/**
*
* in null null
* I/O ClassNotFoundException
* null
*
* @param in ClassNotFoundException
* @return null
*/
@Override @Override
public Object deserialize(byte[] in) { public Object deserialize(byte[] in) {
Object result = null; Object result = null;
ByteArrayInputStream bis = null; ByteArrayInputStream bis = null;
ObjectInputStream is = null; ObjectInputStream is = null;
try { try {
if (in != null) { // 首先判断传入的字节数组是否为 null如果是则直接返回 null不进行后续的反序列化操作因为没有有效的数据可供反序列化。
if (in!= null) {
// 创建一个 ByteArrayInputStream 对象,它用于从字节数组中读取数据,作为反序列化的数据源,将传入的字节数组作为参数传入,初始化流对象。
bis = new ByteArrayInputStream(in); bis = new ByteArrayInputStream(in);
// 创建一个 ObjectInputStream 对象,它用于从字节流中读取并还原对象,将其关联到 ByteArrayInputStream以便从字节数组对应的流中读取数据进行反序列化操作。
is = new ObjectInputStream(bis); is = new ObjectInputStream(bis);
// 通过 ObjectInputStream 的 readObject 方法进行反序列化操作,从流中读取数据并尝试还原为对应的对象,将还原后的对象赋值给 result 变量。
result = is.readObject(); result = is.readObject();
// 关闭 ObjectInputStream释放相关资源同样关闭流时可能出现 IOException需要进行异常处理并且在 finally 块中会再次确保流的关闭。
is.close(); is.close();
// 关闭 ByteArrayInputStream释放资源也可能出现 IOException同样在 finally 块中保障其正确关闭。
bis.close(); bis.close();
} }
} catch (IOException e) { } catch (IOException e) {
// 如果在反序列化过程中出现 I/O 异常(比如流读取失败、关闭流出错等情况),则打印异常堆栈信息进行简单的错误提示,不过当前方法仍会继续执行并返回 null
// 这种处理方式可能导致反序列化失败后调用者难以察觉具体问题,可根据业务实际情况优化,比如向上抛出异常让调用者处理等。
e.printStackTrace(); e.printStackTrace();
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
// 如果出现 ClassNotFoundException 异常,意味着在反序列化时找不到对应的类定义(可能是类所在的 JAR 包未引入、类名更改等原因),同样打印异常堆栈信息进行简单提示,
// 方法继续执行并返回 null可根据实际需求改进处理方式比如更明确地告知调用者类找不到导致反序列化失败等情况。
e.printStackTrace(); e.printStackTrace();
} finally { } finally {
// 调用 close 方法关闭 ObjectInputStream确保资源能正确释放无论前面是否出现异常都要执行关闭操作避免资源泄漏。
close(is); close(is);
// 调用 close 方法关闭 ByteArrayInputStream保障资源的正确关闭和释放。
close(bis); close(bis);
} }
return result; return result;
} }
/**
*
* o1 == o2 true
* null null false equals equals
* Java
*
* @param o1 null
* @param o2 null
* @return true false
*/
public static boolean equals(Object o1, Object o2) { public static boolean equals(Object o1, Object o2) {
// 首先判断两个对象引用是否相同,如果是则表示它们是同一个对象,直接返回 true说明对象相等。
if (o1 == o2) { if (o1 == o2) {
return true; return true;
} else if (o1 == null || o2 == null) { } else if (o1 == null || o2 == null) {
// 如果两个对象引用不相同,接着判断是否有一个对象为 null如果有则返回 false表示两个对象不相等因为只有两个非 null 对象才有进一步比较内容相等的必要。
return false; return false;
} else { } else {
// 如果两个对象都不为 null且引用不同那么调用对象的 equals 方法(这里要求对象所属类正确重写了 equals 方法,遵循 Java 中 equals 方法的重写规范)来比较对象内容是否相等,
// 返回相应的比较结果,以此确定两个对象是否真正相等。
return o1.equals(o2); return o1.equals(o2);
} }
} }
} }

@ -5,85 +5,116 @@ import java.util.List;
import com.baomidou.mybatisplus.plugins.Page; import com.baomidou.mybatisplus.plugins.Page;
// PageUtils类主要用于对分页相关的数据进行处理和封装将MyBatis Plus中Page对象里的分页信息以及对应的数据进行提取、整理
// 并提供了方便获取和设置分页相关属性(如是否显示上一页/下一页按钮、页码列表、当前页码、总页数、总数量等)的方法,方便在前端展示分页信息以及进行分页交互操作。
public class PageUtils { public class PageUtils {
// 是否下一页按钮 // 用于标识是否显示下一页按钮,初始值为 false后续会根据实际的分页情况当前页是否小于总页数来设置其值用于前端判断是否展示下一页的操作按钮。
private Boolean isShowNextBtn = false; private Boolean isShowNextBtn = false;
// 是否上一页按钮 // 用于标识是否显示上一页按钮,初始值为 false同样会依据当前页与总页数等分页情况来确定其值方便前端判断是否展示上一页的操作按钮。
private Boolean isShowPreBtn = false; private Boolean isShowPreBtn = false;
// 当前页 // 存储当前页码的字符串表示形式会从传入的Page对象中获取当前页码并转换为字符串进行存储用于前端展示当前所在的页码信息。
private String currPageNum; private String currPageNum;
// 页码列表 // 用于存储页码列表的字符串集合,例如 ["1", "2", "3", "...", "5", "6", "7"] 这样的形式,表示分页的页码展示情况,会根据总页数等条件动态生成该列表,方便前端展示分页页码导航。
private List<String> pageNums; private List<String> pageNums;
// 总页数 // 存储总页数的字符串表示形式从Page对象中获取总页数并转换为字符串保存用于告知前端总共有多少页方便进行分页范围的判断等操作。
private String totalPage; private String totalPage;
// 总数量 // 存储数据总量的字符串表示形式从Page对象中获取总记录数并转换为字符串用于在前端展示总共有多少条数据让用户对数据规模有直观了解。
private String total; private String total;
// 数据 // 用于存储当前页的数据列表类型为泛型List<?>实际存放的是从Page对象中获取的对应页的记录数据方便在前端展示该页的具体数据内容。
private List<?> list; private List<?> list;
public static PageUtils getPage(Page<?> page){ /**
* PagePageUtils
* MyBatis PlusPage<?>PagePageUtils
* /PageUtils便使
*
* @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){ // 判断传入的Page对象的当前页码page.getCurrent()是否大于1如果大于1说明不是第一页此时应该显示上一页按钮所以将PageUtils实例中的isShowPreBtn属性设置为true。
if (page.getCurrent() > 1) {
pg.setIsShowPreBtn(true); pg.setIsShowPreBtn(true);
} }
if(page.getCurrent() < page.getPages()){ // 判断传入的Page对象的当前页码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){ // 判断总页数page.getPages()是否大于1如果大于1才需要构建页码列表因为如果只有1页就不需要展示页码导航等信息了。
if(page.getPages() > 10){ 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()); if (page.getCurrent() == page.getPages()) {
pgNums.add(((Integer)(page.getCurrent() - 1)).toString()); pgNums.add(((Integer) (page.getCurrent() - 2)).toString());
pgNums.add(((Integer)page.getCurrent()).toString()); pgNums.add(((Integer) (page.getCurrent() - 1)).toString());
}else{ pgNums.add(((Integer) page.getCurrent()).toString());
pgNums.add(((Integer)(page.getCurrent() - 1)).toString()); } else {
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页采用简单的顺序添加页码的方式生成页码列表从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++;
} }
} }
} }
} else { } else {
// 如果总页数等于1页同样采用顺序添加页码的方式这里其实只会添加页码 "1"生成页码列表从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属性中以便后续通过相应的获取方法getPageNums提供给外部使用展示分页页码信息。
pg.setPageNums(pgNums); pg.setPageNums(pgNums);
// 将Page对象中当前页的数据列表page.getRecords()设置到PageUtils实例的list属性中方便后续获取该页的数据用于前端展示等操作。
pg.setList(page.getRecords()); pg.setList(page.getRecords());
pg.setCurrPageNum(((Integer)page.getCurrent()).toString()); // 将Page对象的当前页码page.getCurrent()转换为字符串后设置到PageUtils实例的currPageNum属性中用于前端展示当前所在页码信息。
pg.setTotal(((Integer)page.getTotal()).toString()); pg.setCurrPageNum(((Integer) page.getCurrent()).toString());
pg.setTotalPage(((Integer)page.getPages()).toString()); // 将Page对象的总记录数page.getTotal()转换为字符串后设置到PageUtils实例的total属性中用于前端展示数据总量信息。
pg.setTotal(((Integer) page.getTotal()).toString());
// 将Page对象的总页数page.getPages()转换为字符串后设置到PageUtils实例的totalPage属性中用于前端展示总页数信息方便用户了解分页的整体范围。
pg.setTotalPage(((Integer) page.getPages()).toString());
return pg; return pg;
} }
// 以下是各个属性的Getter和Setter方法遵循JavaBean规范方便外部类对PageUtils实例中的属性进行获取和设置操作保证数据的封装性和可访问性。
public Boolean getIsShowNextBtn() { public Boolean getIsShowNextBtn() {
return isShowNextBtn; return isShowNextBtn;
@ -109,42 +140,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 String getCurrPageNum() { public String getCurrPageNum() {
return currPageNum; return currPageNum;
} }
public void setCurrPageNum(String currPageNum) { public void setCurrPageNum(String currPageNum) {
this.currPageNum = currPageNum; this.currPageNum = currPageNum;
} }
public String getTotalPage() { public String getTotalPage() {
return totalPage; return totalPage;
} }
public void setTotalPage(String totalPage) { public void setTotalPage(String totalPage) {
this.totalPage = totalPage; this.totalPage = totalPage;
} }
public String getTotal() { public String getTotal() {
return total; return total;
} }
public void setTotal(String total) { public void setTotal(String total) {
this.total = total; this.total = total;
} }

@ -6,36 +6,54 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.log4j.Logger; import org.apache.log4j.Logger;
// RequestHelper类主要用于辅助处理与HTTP请求相关的一些通用操作当前类中仅展示了一个获取客户端IP地址的方法
// 该方法会尝试从不同的请求头信息中获取IP地址以应对客户端通过代理服务器等方式访问时获取真实IP地址的情况同时会进行相应的日志记录。
public class RequestHelper { public class RequestHelper {
// 创建一个Logger对象用于记录与这个类相关的日志信息这里将日志记录器的名称设置为RequestHelper类的全限定名
// 通过这个日志记录器可以在合适的地方输出不同级别的日志如INFO级别等方便调试和查看相关操作信息例如记录获取IP地址的过程和结果等情况。
private static Logger logger = Logger.getLogger(RequestHelper.class); private static Logger logger = Logger.getLogger(RequestHelper.class);
/** /**
* IP,IP; * IPIP
* HTTPHttpServletRequestIPIP
* 访IP request.getRemoteAddr()
* IPIPIPIPIP
* *
* @param request * @param request HttpServletRequestHTTPIPIP
* @return * @return IPIP request.getRemoteAddr() IP
* @throws IOException * @throws IOException I/O
*/ */
public final static String getIpAddress(HttpServletRequest request) { public final static String getIpAddress(HttpServletRequest request) {
// 获取请求主机IP地址,如果通过代理进来则透过防火墙获取真实IP地址 // 首先尝试从 "X-Forwarded-For" 请求头字段中获取IP地址这个请求头通常在客户端通过代理服务器访问时由代理服务器添加用于记录原始客户端的IP地址以及经过的代理服务器IP地址列表以逗号分隔
// 如果该请求头不存在或者其值为空字符串或者值为 "unknown"表示无法获取到有效IP地址的一种约定情况则继续尝试从其他请求头获取IP地址。
String ip = request.getHeader("X-Forwarded-For"); String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 如果从 "X-Forwarded-For" 请求头中未获取到有效IP地址再次尝试从 "Proxy-Client-IP" 请求头获取IP地址
// 某些代理服务器会使用这个请求头来传递客户端的IP地址信息同样判断其是否为空或者值为 "unknown",如果是则继续尝试下一个请求头。
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP"); ip = request.getHeader("Proxy-Client-IP");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 接着尝试从 "WL-Proxy-Client-IP" 请求头获取IP地址这是WebLogic服务器作为代理时可能使用的请求头来传递客户端IP地址继续进行有效性判断和后续尝试。
ip = request.getHeader("WL-Proxy-Client-IP"); ip = request.getHeader("WL-Proxy-Client-IP");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 再尝试从 "HTTP_CLIENT_IP" 请求头获取IP地址一些其他的代理服务器或者特定的网络环境可能会通过这个请求头传递客户端IP信息同样检查是否有效并按需继续查找。
ip = request.getHeader("HTTP_CLIENT_IP"); ip = request.getHeader("HTTP_CLIENT_IP");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 然后尝试从 "HTTP_X_FORWARDED_FOR" 请求头获取IP地址它与 "X-Forwarded-For" 类似也是可能用于记录客户端IP经过代理的相关情况的请求头继续判断并尝试获取有效IP地址。
ip = request.getHeader("HTTP_X_FORWARDED_FOR"); ip = request.getHeader("HTTP_X_FORWARDED_FOR");
} }
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) { if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
// 如果前面所有尝试从请求头获取IP地址的方式都失败了最后通过 request.getRemoteAddr() 获取IP地址
// 这个方法通常获取的是与服务器直接建立连接的客户端可能是代理服务器或者真实客户端如果没有代理的话的IP地址作为一种兜底获取IP地址的方式。
ip = request.getRemoteAddr(); ip = request.getRemoteAddr();
} }
} else if (ip.length() > 15) { } else if (ip.length() > 15) {
// 如果从 "X-Forwarded-For" 请求头中获取到的IP地址字符串长度大于15说明可能包含了多个IP地址以逗号分隔的情况记录了经过的多个代理服务器IP等情况
// 需要对其进行处理将IP地址字符串按照逗号分割为字符串数组然后遍历数组查找第一个不为 "unknown" 的IP地址将其作为客户端的真实IP地址因为按照约定真实客户端IP地址通常在最前面。
String[] ips = ip.split(","); String[] ips = ip.split(",");
for (int index = 0; index < ips.length; index++) { for (int index = 0; index < ips.length; index++) {
String strIp = (String) ips[index]; String strIp = (String) ips[index];
@ -45,9 +63,13 @@ public class RequestHelper {
} }
} }
} }
// 判断日志记录器的INFO级别是否启用通过配置文件等方式设置日志级别如果启用了INFO级别日志
// 则记录一条INFO级别的日志信息将获取到的IP地址信息以及方法相关的标识信息记录下来方便后续查看获取IP地址的具体情况例如排查IP地址获取是否正确等问题。
if (logger.isInfoEnabled()) { if (logger.isInfoEnabled()) {
logger.info("getIpAddress(HttpServletRequest) - Proxy-Client-IP - String ip=" + ip); logger.info("getIpAddress(HttpServletRequest) - Proxy-Client-IP - String ip=" + ip);
} }
return ip; return ip;
} }
} }

@ -5,23 +5,33 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
// Result类用于封装操作结果的通用类实现了Serializable接口意味着该类的对象可以被序列化方便在网络传输、持久化存储等场景下使用。
// 它定义了表示操作结果的相关属性(如状态码、具体结果数据、提示信息等)以及一系列静态方法来便捷地创建不同类型(成功或失败)的结果对象,同时提供了一些用于构建特定格式结果数据的方法,适用于构建接口返回数据等情况。
public class Result implements Serializable { public class Result implements Serializable {
// 序列化版本号,用于在对象序列化和反序列化过程中保证版本兼容性,当类的结构发生变化(如新增、删除字段等情况)时,若版本号不一致可能导致反序列化失败,这里是一个固定的长整型数值。
private static final long serialVersionUID = -1651614836984397356L; private static final long serialVersionUID = -1651614836984397356L;
// 用于表示操作结果的状态码不同的整数值代表不同的操作结果状态例如常见的用0表示成功1表示失败等方便调用者根据状态码判断操作是否成功以及进行相应的后续处理。
private int code; private int code;
// 用于存储操作的具体结果数据,可以是任意类型的对象(如实体对象、数据集合等),根据具体的业务操作来决定存放的内容,方便将业务相关的数据返回给调用者。
private Object result; private Object result;
// 用于存储与操作结果相关的提示信息,比如操作成功时可以为空字符串,操作失败时存放具体的错误提示内容,便于告知调用者操作的执行情况。
private String message; private String message;
// 定义表示成功的状态码常量方便在创建成功结果对象时统一使用使代码更具可读性和可维护性当前设置为0表示操作成功的情况。
public static final int SUCCESS_CODE = 0; public static final int SUCCESS_CODE = 0;
// 定义表示失败的状态码常量同样用于在创建失败结果对象时保持一致性当前设定为1用于标识操作出现问题、未成功执行的情况。
public static final int FAIL_CODE = 1; public static final int FAIL_CODE = 1;
// 私有构造函数用于限制外部直接通过构造函数创建Result对象保证结果对象的创建只能通过类中提供的静态方法来进行遵循封装的设计原则便于统一管理结果对象的创建逻辑。
private Result() { private Result() {
} }
// 私有构造函数用于在类内部创建Result对象时传入状态码、具体结果数据以及提示信息通过这种方式初始化Result对象的各个属性同样外部无法直接调用这个构造函数只能通过静态方法间接使用。
private Result(int code, Object result, String message) { private Result(int code, Object result, String message) {
this.code = code; this.code = code;
this.result = result; this.result = result;
@ -29,67 +39,139 @@ public class Result implements Serializable {
} }
/** /**
* *
* ResultresultSUCCESS_CODE0
* ResultresultResult便
* *
* @param result * @param result
* @return * @return Result
*/ */
public static Result successResult(Object result) { public static Result successResult(Object result) {
return result(SUCCESS_CODE, result, ""); return result(SUCCESS_CODE, result, "");
} }
/**
*
* successResultResultrecordsrecordSumrowsOfPage
* successResultMapResult便
*
* @param records
* @param recordSum 便
* @param rowsOfPage
* @return Result
*/
public static Result successResult(Object records, Long recordSum, Long rowsOfPage) { public static Result successResult(Object records, Long recordSum, Long rowsOfPage) {
return successResult(records, recordSum, rowsOfPage, null); return successResult(records, recordSum, rowsOfPage, null);
} }
/**
*
* successResultuserDataResult
* resultOfListMapMapsuccessResult
* Result便
*
* @param records
* @param recordSum
* @param rowsOfPage 便
* @param userData
* @return Result
*/
public static Result successResult(Object records, Long recordSum, Long rowsOfPage, Object userData) { public static Result successResult(Object records, Long recordSum, Long rowsOfPage, Object userData) {
Map<String, Object> result = resultOfList(records, recordSum, rowsOfPage, userData); Map<String, Object> result = resultOfList(records, recordSum, rowsOfPage, userData);
return successResult(result); return successResult(result);
} }
/**
* Map
* Map便
* MapMapResult使
*
* @param records
* @param recordSum 便
* @param rowsOfPage
* @return Map"rows""records""total"
*/
public static Map<String, Object> resultOfList(Object records, Long recordSum, Long rowsOfPage) { public static Map<String, Object> resultOfList(Object records, Long recordSum, Long rowsOfPage) {
return resultOfList(records, recordSum, rowsOfPage, null); return resultOfList(records, recordSum, rowsOfPage, null);
} }
/**
* Map
* userDataMapHashMap
* "rows""records""total""userdata"便
* MapResult使
*
* @param Obj
* @param records
* @param rowsOfPage 便
* @param userData
* @return Map"rows""records""total""userdata"便使
*/
public static Map<String, Object> resultOfList(Object Obj, Long records, Long rowsOfPage, Object userData) { public static Map<String, Object> resultOfList(Object Obj, Long records, Long rowsOfPage, Object userData) {
Map<String, Object> result = new HashMap<String, Object>(); Map<String, Object> result = new HashMap<String, Object>();
result.put("rows", Obj); result.put("rows", Obj);
result.put("records", records); result.put("records", records);
result.put("total", rowsOfPage); result.put("total", rowsOfPage);
if (null != userData) { if (null!= userData) {
result.put("userdata", userData); result.put("userdata", userData);
} }
;
return result; return result;
} }
/**
* jqGridMap
* jqGridMap
* HashMapjqGrid "list""totalCount""pageSize""currPage""totalPage"
* 便使jqGridMap
*
* @param list jqGrid
* @param totalCount jqGrid便jqGrid
* @param pageSize jqGrid便
* @param currPage jqGrid便
* @param totalPage jqGrid
* @return MapjqGrid"list""totalCount""pageSize""currPage""totalPage"便使jqGrid
*/
public static Map<String, Object> jqGridResult(List<?> list, long totalCount, int pageSize, int currPage, public static Map<String, Object> jqGridResult(List<?> list, long totalCount, int pageSize, int currPage,
int totalPage) { int totalPage) {
Map<String, Object> result = new HashMap<String, Object>(); Map<String, Object> result = new HashMap<String, Object>();
result.put("list", list); result.put("list", list);
result.put("totalCount", totalCount); result.put("totalCount", totalCount);
result.put("totalPage", totalPage);
result.put("pageSize", pageSize); result.put("pageSize", pageSize);
result.put("currPage", currPage); result.put("currPage", currPage);
result.put("totalPage", totalPage);
return result; return result;
} }
/** /**
* *
* ResulterrorMsgFAIL_CODE1
* ResultmessageresultResult便
* *
* @param errorMsg * @param errorMsg
* @return * @return Result
*/ */
public static Result failResult(String errorMsg) { public static Result failResult(String errorMsg) {
return result(FAIL_CODE, "", errorMsg); return result(FAIL_CODE, "", errorMsg);
} }
/**
*
* Resultcoderesultmessage
* ResultResult便
*
* @param code 01
* @param result
* @param message
* @return Result
*/
public static Result result(int code, Object result, String message) { public static Result result(int code, Object result, String message) {
Result res = new Result(code, result, message); Result res = new Result(code, result, message);
return res; return res;
} }
// 以下是Result类中各个属性的Getter方法遵循JavaBean规范方便外部类获取Result对象中封装的状态码、结果数据以及提示信息等内容保证数据的封装性和可访问性。
public int getCode() { public int getCode() {
return code; return code;
} }
@ -101,5 +183,4 @@ public class Result implements Serializable {
public String getMessage() { public String getMessage() {
return message; return message;
} }
} }

@ -8,253 +8,153 @@ import org.apache.commons.codec.binary.Base64;
import org.apache.commons.fileupload.*; import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException; import org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException;
import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException; import org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException;
import org.apache.commons.fileupload.util.*; import org.apache.commons.fileupload.util.Streams;
import org.apache.commons.fileupload.servlet.*; import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.fileupload.FileItemIterator; import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.disk.DiskFileItemFactory; import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
/**
* UEditor // Uploader类是一个UEditor文件上传辅助类用于处理文件上传的各种操作包括文件格式检查、大小限制判断、文件保存路径处理以及实际的文件上传逻辑等
* // 同时支持常规的表单文件上传和接收Base64格式文件上传两种方式并且会根据上传过程中的不同情况设置相应的状态信息方便调用者知晓上传结果。
*/
public class Uploader { public class Uploader {
// 输出文件地址
// 用于存储上传文件的输出文件地址,即文件最终保存的相对路径或者完整路径(根据具体使用场景确定),初始值为空字符串,在文件上传成功后会被赋值为实际的保存路径。
private String url = ""; private String url = "";
// 上传文件名 // 用于存储上传后的文件名,这个文件名可能是经过处理后生成的新文件名(例如添加随机数、时间戳等以保证文件名的唯一性等情况),初始值为空字符串,上传成功后会被赋予实际的文件名。
private String fileName = ""; private String fileName = "";
// 状态 // 用于表示文件上传的状态通过设置不同的状态值从errorInfo这个HashMap中获取对应的状态描述字符串来反馈上传过程中出现的各种情况例如成功、文件格式错误、大小超出限制等初始值为空字符串会在上传操作过程中根据情况进行更新。
private String state = ""; private String state = "";
// 文件类型 // 用于存储文件类型,一般通过文件扩展名来确定,例如 ".jpg"、".pdf" 等,在文件上传过程中会进行相应的提取和赋值操作,初始值为空字符串。
private String type = ""; private String type = "";
// 原始文件名 // 用于存储上传文件的原始文件名,即用户在客户端选择文件时的原始文件名称,方便记录和后续可能的展示等操作,初始值为空字符串,上传时会获取并赋值。
private String originalName = ""; private String originalName = "";
// 文件大小 // 用于存储上传文件大小单位为字节在文件上传完成后会获取并记录文件的实际大小初始值为0。
private long size = 0; private long size = 0;
// 用于接收来自客户端的HTTP请求对象HttpServletRequest通过这个对象可以获取上传文件相关的信息如请求头、请求参数等在整个文件上传过程中起着关键的信息获取作用初始值为null通过构造函数传入。
private HttpServletRequest request = null; private HttpServletRequest request = null;
// 用于存储文件的标题信息(可能在某些特定的业务场景下使用,例如图片有对应的标题描述等情况),初始值为空字符串,会在处理表单中对应字段时进行赋值。
private String title = ""; private String title = "";
// 保存路径 // 用于指定文件的保存路径,默认值为 "upload"表示文件将会被保存到这个相对路径下实际的物理路径会根据服务器配置等情况进一步确定可以通过相应的Setter方法进行修改设置。
private String savePath = "upload"; private String savePath = "upload";
// 文件允许格式 // 用于定义允许上传的文件格式数组,只有文件名后缀符合这个数组中定义的格式的文件才允许被上传,初始包含了常见的一些文件格式如压缩文件、文档文件、图片文件等类型的后缀名可通过Setter方法重新设置允许的文件格式。
private String[] allowFiles = { ".rar", ".doc", ".docx", ".zip", ".pdf",".txt", ".swf", ".wmv", ".gif", ".png", ".jpg", ".jpeg", ".bmp" }; private String[] allowFiles = { ".rar", ".doc", ".docx", ".zip", ".pdf", ".txt", ".swf", ".wmv", ".gif", ".png", ".jpg", ".jpeg", ".bmp" };
// 文件大小限制单位KB // 用于设置文件大小限制,单位KB默认值为10000KB即10MB如果上传文件的大小超过这个限制将会在上传过程中被拦截并设置相应的错误状态可通过Setter方法调整文件大小限制值。
private int maxSize = 10000; private int maxSize = 10000;
// 用于存储各种文件上传错误信息的HashMap键为表示错误类型的字符串如 "SUCCESS"、"NOFILE" 等),值为对应的详细错误描述字符串,方便根据不同的上传情况设置相应的状态信息并反馈给调用者具体的错误原因。
private HashMap<String, String> errorInfo = new HashMap<String, String>(); private HashMap<String, String> errorInfo = new HashMap<String, String>();
/**
* Uploader
* HttpServletRequestrequest便
* errorInfoHashMap
*
* @param request HttpServletRequestHTTP
*/
public Uploader(HttpServletRequest request) { public Uploader(HttpServletRequest request) {
this.request = request; this.request = request;
HashMap<String, String> tmp = this.errorInfo; HashMap<String, String> tmp = this.errorInfo;
tmp.put("SUCCESS", "SUCCESS"); //默认成功 tmp.put("SUCCESS", "SUCCESS"); //默认成功,设置表示上传成功的状态对应的描述信息为 "SUCCESS",方便后续判断和返回结果时使用。
tmp.put("NOFILE", "未包含文件上传域"); tmp.put("NOFILE", "未包含文件上传域"); // 设置表示请求中未包含文件上传相关字段的错误状态对应的描述信息,用于在没有文件上传时反馈相应的错误情况。
tmp.put("TYPE", "不允许的文件格式"); tmp.put("TYPE", "不允许的文件格式"); // 设置表示上传文件的格式不符合允许的文件格式列表allowFiles的错误状态对应的描述信息用于文件格式验证不通过时反馈错误原因。
tmp.put("SIZE", "文件大小超出限制"); tmp.put("SIZE", "文件大小超出限制"); // 设置表示上传文件大小超过了设定的最大文件大小限制maxSize的错误状态对应的描述信息用于文件大小超出限制时告知调用者具体错误情况。
tmp.put("ENTYPE", "请求类型ENTYPE错误"); tmp.put("ENTYPE", "请求类型ENTYPE错误"); // 设置表示请求的类型如Content-Type等相关请求头不符合文件上传要求出现错误的状态对应的描述信息用于请求类型不合法时反馈错误情况。
tmp.put("REQUEST", "上传请求异常"); tmp.put("REQUEST", "上传请求异常"); // 设置表示在文件上传请求处理过程中出现其他未知异常的错误状态对应的描述信息,用于捕获到一般性的请求相关异常时反馈错误原因。
tmp.put("IO", "IO异常"); tmp.put("IO", "IO异常"); // 设置表示在文件输入输出如读取文件、写入文件等操作过程中出现I/O异常的错误状态对应的描述信息用于处理文件读写等操作出现问题时反馈错误情况。
tmp.put("DIR", "目录创建失败"); tmp.put("DIR", "目录创建失败"); // 设置表示在创建文件保存目录(例如根据日期创建子目录等情况)时失败的错误状态对应的描述信息,用于目录创建出现问题时反馈错误原因。
tmp.put("UNKNOWN", "未知错误"); tmp.put("UNKNOWN", "未知错误"); // 设置表示其他未明确归类的、未知的错误情况对应的错误状态描述信息,用于捕获到意料之外的异常时进行统一的错误反馈。
} }
/**
*
* NOFILE
* DiskFileItemFactoryServletFileUpload
*
*
* "pictitle" UE退
*
* @throws Exception Exception
*/
public void upload() throws Exception { public void upload() throws Exception {
// 判断当前请求是否为多部分表单数据即是否包含文件上传域通过ServletFileUpload提供的静态方法进行判断这是进行文件上传操作的前提条件如果不是多部分表单数据则不能进行后续的文件上传处理。
boolean isMultipart = ServletFileUpload.isMultipartContent(this.request); boolean isMultipart = ServletFileUpload.isMultipartContent(this.request);
if (!isMultipart) { if (!isMultipart) {
// 如果请求不是多部分表单数据,说明没有文件上传相关内容,设置文件上传状态为 "NOFILE"(表示未包含文件上传域),然后直接返回,不再进行后续的文件上传操作流程。
this.state = this.errorInfo.get("NOFILE"); this.state = this.errorInfo.get("NOFILE");
return; return;
} }
// 创建一个DiskFileItemFactory对象它用于配置文件上传过程中的一些基础设置例如设置临时文件的存储位置等这里后续会将其与ServletFileUpload结合使用来处理文件上传操作。
DiskFileItemFactory dff = new DiskFileItemFactory(); DiskFileItemFactory dff = new DiskFileItemFactory();
// 获取文件的保存路径通过调用getFolder方法根据配置的保存路径this.savePath以及当前日期生成具体的保存目录路径确保文件按照日期进行分类保存同时也处理了目录创建等相关操作。
String savePath = this.getFolder(this.savePath); String savePath = this.getFolder(this.savePath);
// 设置DiskFileItemFactory的临时文件存储目录将其指定为前面获取到的保存路径对应的File对象这样在文件上传过程中如果需要临时存储文件例如文件较大时分块处理等情况会将临时文件存放在这个目录下。
dff.setRepository(new File(savePath)); dff.setRepository(new File(savePath));
try { try {
// 创建一个ServletFileUpload对象用于处理文件上传的具体操作将前面配置好的DiskFileItemFactory对象传入使其基于配置好的基础设置来进行文件上传相关的处理工作如解析请求、获取文件项等。
ServletFileUpload sfu = new ServletFileUpload(dff); ServletFileUpload sfu = new ServletFileUpload(dff);
// 设置文件上传的最大允许大小通过将以KB为单位的maxSize乘以1024转换为字节单位限制上传文件的大小不能超过这个设定值若超过则会抛出SizeLimitExceededException异常进行相应的错误处理。
sfu.setSizeMax(this.maxSize * 1024); sfu.setSizeMax(this.maxSize * 1024);
// 设置请求头的编码格式为 "utf-8",确保在处理包含中文等特殊字符的文件名等信息时能够正确解析,避免出现乱码问题,保证文件上传过程中字符编码的一致性。
sfu.setHeaderEncoding("utf-8"); sfu.setHeaderEncoding("utf-8");
// 通过ServletFileUpload的getItemIterator方法获取一个FileItemIterator对象它用于迭代请求中的文件项包括上传的文件以及表单中的其他字段信息等方便后续逐个进行处理。
FileItemIterator fii = sfu.getItemIterator(this.request); FileItemIterator fii = sfu.getItemIterator(this.request);
while (fii.hasNext()) { while (fii.hasNext()) {
// 获取下一个文件项返回的FileItemStream对象可以用于获取文件项的相关信息如文件名、文件内容流等以及判断是否为表单字段等操作是处理文件上传过程中每个文件或字段的关键对象。
FileItemStream fis = fii.next(); FileItemStream fis = fii.next();
if (!fis.isFormField()) { if (!fis.isFormField()) {
// 如果当前文件项不是表单字段(即代表是一个真正要上传的文件),则进行以下文件上传相关的处理操作。
// 获取上传文件的原始文件名通过截取文件名中最后一个文件分隔符根据系统的文件分隔符来获取通过System.getProperty("file.separator")获取系统对应的文件分隔符)之后的部分作为原始文件名,
// 去除了文件的路径信息,只保留文件名本身,方便后续处理和记录。
this.originalName = fis.getName().substring(fis.getName().lastIndexOf(System.getProperty("file.separator")) + 1); this.originalName = fis.getName().substring(fis.getName().lastIndexOf(System.getProperty("file.separator")) + 1);
// 检查文件的格式是否符合允许的文件格式列表allowFiles调用checkFileType方法进行检查如果不符合则设置文件上传状态为 "TYPE"(表示不允许的文件格式),并跳过当前文件的后续上传操作,继续处理下一个文件项(如果有的话)。
if (!this.checkFileType(this.originalName)) { if (!this.checkFileType(this.originalName)) {
this.state = this.errorInfo.get("TYPE"); this.state = this.errorInfo.get("TYPE");
continue; continue;
} }
// 生成新的文件名调用getName方法根据原始文件名生成一个带有随机数、时间戳等信息的新文件名以保证文件名的唯一性和避免文件名冲突等问题同时将新文件名赋值给fileName成员变量。
this.fileName = this.getName(this.originalName); this.fileName = this.getName(this.originalName);
// 获取文件的类型通过调用getFileExt方法截取文件名的后缀部分从最后一个 "." 开始截取到末尾)作为文件类型,例如 ".jpg"、".pdf" 等并赋值给type成员变量方便后续记录和使用。
this.type = this.getFileExt(this.fileName); this.type = this.getFileExt(this.fileName);
// 构建文件的输出文件地址即文件最终保存的路径将保存路径savePath和新生成的文件名fileName组合起来形成完整的文件保存路径赋值给url成员变量用于后续保存文件时确定保存位置。
this.url = savePath + "/" + this.fileName; this.url = savePath + "/" + this.fileName;
// 创建一个缓冲输入流BufferedInputStream用于读取上传文件的内容通过调用fis.openStream方法打开文件项对应的输入流并将其包装为缓冲输入流提高文件读取效率准备将文件内容读取出来进行保存操作。
BufferedInputStream in = new BufferedInputStream(fis.openStream()); BufferedInputStream in = new BufferedInputStream(fis.openStream());
// 根据前面构建的文件保存路径创建一个对应的File对象用于表示要保存的目标文件方便后续通过文件输出流将读取到的文件内容写入到这个文件中完成文件的保存操作。
File file = new File(this.getPhysicalPath(this.url)); File file = new File(this.getPhysicalPath(this.url));
FileOutputStream out = new FileOutputStream( file ); // 创建一个文件输出流FileOutputStream用于将文件内容写入到目标文件中将前面创建的File对象作为参数传入初始化文件输出流对象准备进行文件写入操作。
FileOutputStream out = new FileOutputStream(file);
// 创建一个缓冲输出流BufferedOutputStream将前面的文件输出流包装起来进一步提高文件写入的效率和稳定性通过缓冲机制优化文件输出操作减少频繁的磁盘I/O操作次数。
BufferedOutputStream output = new BufferedOutputStream(out); BufferedOutputStream output = new BufferedOutputStream(out);
// 使用Streams工具类来自Apache Commons FileUpload组件的copy方法将输入流in即读取的上传文件内容中的数据复制到输出流output即要写入到目标文件的输出流实现文件的保存操作
// 最后一个参数true表示在复制完成后自动关闭输入流和输出流释放相关资源确保文件内容正确地从客户端上传并保存到服务器指定的位置。
Streams.copy(in, output, true); Streams.copy(in, output, true);
this.state=this.errorInfo.get("SUCCESS");
// 如果文件上传成功,设置文件上传状态为 "SUCCESS"表示成功通过从errorInfo这个HashMap中获取对应的成功状态描述字符串来设置告知调用者文件上传操作顺利完成。
this.state = this.errorInfo.get("SUCCESS");
// 获取并记录上传文件的大小通过获取保存后的文件的长度单位为字节赋值给size成员变量方便后续获取文件大小信息进行相关的展示或其他业务操作。
this.size = file.length(); this.size = file.length();
//UE中只会处理单张上传完成后即退出
// 在UEUEditor一种富文本编辑器推测此处是针对其文件上传逻辑进行的代码编写中按只处理单张上传的逻辑所以一旦成功上传一个文件后就直接退出循环不再处理后续的文件项如果还有的话
break; break;
} else { } else {
// 如果当前文件项是表单字段,则进行以下处理操作(当前代码中仅处理名为 "pictitle" 的字段作为标题字段,对于其他表单字段可以根据业务需求自行扩展处理逻辑)。
// 获取表单字段的名称通过调用fis.getFieldName方法获取字段的名称用于后续判断是否是需要处理的特定字段如这里的 "pictitle")。
String fname = fis.getFieldName(); String fname = fis.getFieldName();
//只处理title其余表单请自行处理 // 判断表单字段名称是否等于 "pictitle",如果不等于,则直接跳过当前字段,继续处理下一个文件项,因为当前代码只关注这个特定的标题字段进行处理。
if(!fname.equals("pictitle")){ if (!fname.equals("pictitle")) {
continue; continue;
} }
BufferedInputStream in = new BufferedInputStream(fis.openStream());
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
StringBuffer result = new StringBuffer();
while (reader.ready()) {
result.append((char)reader.read());
}
this.title = new String(result.toString().getBytes(),"utf-8");
reader.close();
}
}
} catch (SizeLimitExceededException e) {
this.state = this.errorInfo.get("SIZE");
} catch (InvalidContentTypeException e) {
this.state = this.errorInfo.get("ENTYPE");
} catch (FileUploadException e) {
this.state = this.errorInfo.get("REQUEST");
} catch (Exception e) {
this.state = this.errorInfo.get("UNKNOWN");
}
}
/**
* base64
* @param fieldName
*/
public void uploadBase64(String fieldName){
String savePath = this.getFolder(this.savePath);
String base64Data = this.request.getParameter(fieldName);
this.fileName = this.getName("test.png");
this.url = savePath + "/" + this.fileName;
try {
File outFile = new File(this.getPhysicalPath(this.url));
OutputStream ro = new FileOutputStream(outFile);
byte[] b = Base64.decodeBase64(base64Data);
for (int i = 0; i < b.length; ++i) {
if (b[i] < 0) {
b[i] += 256;
}
}
ro.write(b);
ro.flush();
ro.close();
this.state=this.errorInfo.get("SUCCESS");
} catch (Exception e) {
this.state = this.errorInfo.get("IO");
}
}
/**
*
*
* @param fileName
* @return
*/
private boolean checkFileType(String fileName) {
Iterator<String> type = Arrays.asList(this.allowFiles).iterator();
while (type.hasNext()) {
String ext = type.next();
if (fileName.toLowerCase().endsWith(ext)) {
return true;
}
}
return false;
}
/**
*
*
* @return string
*/
private String getFileExt(String fileName) {
return fileName.substring(fileName.lastIndexOf("."));
}
/**
*
* @return
*/
private String getName(String fileName) {
Random random = new Random();
return this.fileName = "" + random.nextInt(10000)
+ System.currentTimeMillis() + this.getFileExt(fileName);
}
/**
*
* @param path
* @return
*/
private String getFolder(String path) {
SimpleDateFormat formater = new SimpleDateFormat("yyyyMMdd");
path += "/" + formater.format(new Date());
File dir = new File(this.getPhysicalPath(path));
if (!dir.exists()) {
try {
dir.mkdirs();
} catch (Exception e) {
this.state = this.errorInfo.get("DIR");
return "";
}
}
return path;
}
/**
*
*
* @param path
* @return
*/
private String getPhysicalPath(String path) {
String servletPath = this.request.getServletPath();
String realPath = this.request.getSession().getServletContext()
.getRealPath(servletPath);
return new File(realPath).getParent() +"/" +path;
}
public void setSavePath(String savePath) {
this.savePath = savePath;
}
public void setAllowFiles(String[] allowFiles) {
this.allowFiles = allowFiles;
}
public void setMaxSize(int size) {
this.maxSize = size;
}
public long getSize() {
return this.size;
}
public String getUrl() {
return this.url;
}
public String getFileName() {
return this.fileName;
}
public String getState() {
return this.state;
}
public String getTitle() {
return this.title;
}
public String getType() {
return this.type;
}
public String getOriginalName() {
return this.originalName;
}
}
// 创建一个缓冲输入流BufferedInputStream用于读取表单字段对应的输入流内容这里假设表单字段中的内容是以流的形式传递的例如文本信息等通过fis.openStream方法打开输入流并包装为缓冲输入流方便后续读取操作。
BufferedInputStream in = new BufferedInputStream(fis.openStream());
// 创建一个BufferedReader对象将前面的缓冲输入流包装为字符流读取器通过指定字符编码为 "utf-8"

@ -19,38 +19,84 @@ import com.tamguo.service.IChapterService;
import com.tamguo.service.ICourseService; import com.tamguo.service.ICourseService;
import com.tamguo.service.ISubjectService; import com.tamguo.service.ISubjectService;
// 标识这是一个Spring的控制器类用于处理与书籍相关的Web请求并返回相应的视图或数据
@Controller @Controller
public class BookController { public class BookController {
// 自动注入IBookService用于处理书籍相关的业务逻辑比如根据ID查询书籍等操作
@Autowired @Autowired
IBookService iBookService; IBookService iBookService;
// 自动注入IChapterService用于处理章节相关的业务逻辑例如查询某书籍下的章节列表等操作
@Autowired @Autowired
IChapterService iChapterService; IChapterService iChapterService;
// 自动注入ISubjectService用于处理学科相关的业务逻辑像根据学科ID查询学科信息等操作
@Autowired @Autowired
ISubjectService iSubjectService; ISubjectService iSubjectService;
// 自动注入ICourseService用于处理课程相关的业务逻辑例如查询课程信息等操作
@Autowired @Autowired
ICourseService iCourseService; ICourseService iCourseService;
/**
* GETuid
* ModelAndView"book"ModelAndView
* "404"ModelAndView404
*
* @param uid
* @param model ModelAndView
* @return ModelAndView
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"book/{uid}"}, method = RequestMethod.GET) @RequestMapping(value = {"book/{uid}"}, method = RequestMethod.GET)
public ModelAndView index(@PathVariable String uid , ModelAndView model) { public ModelAndView index(@PathVariable String uid, ModelAndView model) {
try { try {
// 根据传入的书籍ID通过书籍服务查询并获取对应的书籍实体信息
BookEntity book = iBookService.selectById(uid); BookEntity book = iBookService.selectById(uid);
// 根据书籍所属的学科ID通过学科服务查询并获取对应的学科实体信息
SubjectEntity subject = iSubjectService.find(book.getSubjectId()); SubjectEntity subject = iSubjectService.find(book.getSubjectId());
// 获取该学科下包含的课程列表信息假设SubjectEntity类中有相应的字段存储课程列表这里获取该列表
List<CourseEntity> courseList = subject.getCourseList(); List<CourseEntity> courseList = subject.getCourseList();
// 通过条件查询,查找与当前书籍属于同一课程的其他书籍列表,用于可能在页面展示相关推荐书籍等场景
List<BookEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", book.getCourseId())); List<BookEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", book.getCourseId()));
// 根据书籍所属的课程ID通过课程服务查询并获取对应的课程实体信息
CourseEntity course = iCourseService.selectById(book.getCourseId()); CourseEntity course = iCourseService.selectById(book.getCourseId());
// 通过条件查询,获取当前书籍下的所有章节列表信息,用于在书籍详情页面展示书籍的章节结构等内容
List<ChapterEntity> chapterList = iChapterService.selectList(Condition.create().eq("book_id", uid)); List<ChapterEntity> chapterList = iChapterService.selectList(Condition.create().eq("book_id", uid));
// 将查询到的书籍实体信息添加到ModelAndView对象中以便在视图中可以获取并展示书籍的详细内容
model.addObject("book", book); model.addObject("book", book);
// 将查询到的学科实体信息添加到ModelAndView对象中以便在视图中展示书籍所属学科等相关信息
model.addObject("subject", subject); model.addObject("subject", subject);
// 将查询到的课程实体信息添加到ModelAndView对象中以便在视图中展示书籍所属课程等相关信息
model.addObject("course", course); model.addObject("course", course);
model.addObject("chapterList" , chapterList);
// 将查询到的书籍章节列表信息添加到ModelAndView对象中以便在视图中展示书籍的章节情况
model.addObject("chapterList", chapterList);
// 将查询到的同课程下的其他书籍列表信息添加到ModelAndView对象中以便在视图中展示相关推荐书籍等内容
model.addObject("courseList", courseList); model.addObject("courseList", courseList);
// 将查询到的本课程下的所有书籍列表信息添加到ModelAndView对象中以便在视图中展示相关推荐书籍等内容
model.addObject("bookList", bookList); model.addObject("bookList", bookList);
// 设置视图名称为"book",对应相应的书籍详情页面模板,后续会根据这个名称去查找并渲染对应的视图
model.setViewName("book"); model.setViewName("book");
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户
return model; return model;
} catch (Exception e) { } catch (Exception e) {
// 如果在查询相关数据过程中出现异常,将视图名称设置为"404",通常用于表示请求的资源不存在等错误情况
model.setViewName("404"); model.setViewName("404");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出404页面展示给用户
return model; return model;
} }
} }

@ -10,9 +10,9 @@ import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
/** /**
* Controller - * Controller -
* SpringCourseWeb
* *
* @author tamguo * @author tamguo
*
*/ */
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
@ -27,53 +27,111 @@ import com.tamguo.service.ICourseService;
import com.tamguo.service.ISubjectService; import com.tamguo.service.ISubjectService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
// 标识这是一个Spring的控制器类用于处理课程相关的Web请求并返回相应的视图或数据
@Controller @Controller
public class CourseController { public class CourseController {
// 自动注入IChapterService用于处理章节相关的业务逻辑例如查询课程对应的章节信息、获取章节树结构等操作
@Autowired @Autowired
IChapterService iChapterService; IChapterService iChapterService;
// 自动注入ICourseService用于处理课程相关的业务逻辑像根据课程ID查找课程、按学科ID查找课程列表等操作
@Autowired @Autowired
ICourseService iCourseService; ICourseService iCourseService;
// 自动注入ISubjectService用于处理学科相关的业务逻辑例如根据学科ID查找学科信息等操作
@Autowired @Autowired
ISubjectService iSubjectService; ISubjectService iSubjectService;
// 自动注入IBookService用于处理书籍相关的业务逻辑比如根据课程ID查询相关书籍列表等操作
@Autowired @Autowired
IBookService iBookService; IBookService iBookService;
/**
* GETuid
* ModelAndView"chapter"ModelAndView
* "404"ModelAndView404
*
* @param uid
* @param model ModelAndView
* @return ModelAndView
*/
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@RequestMapping(value = {"course/{uid}"}, method = RequestMethod.GET) @RequestMapping(value = {"course/{uid}"}, method = RequestMethod.GET)
public ModelAndView index(@PathVariable String uid , ModelAndView model) { public ModelAndView index(@PathVariable String uid, ModelAndView model) {
try { try {
// 根据传入的课程ID通过课程服务查询并获取对应的课程实体信息
CourseEntity course = iCourseService.find(uid); CourseEntity course = iCourseService.find(uid);
// 通过条件查询,查找与当前课程相关的所有书籍列表,可能用于展示该课程下有哪些相关书籍等场景
List<BookEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", uid)); List<BookEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", uid));
// 获取书籍列表中的第一本图书实体信息(这里假设取第一本作为某种默认展示或后续处理的依据,具体业务逻辑可能需根据实际情况调整)
BookEntity book = bookList.get(0); BookEntity book = bookList.get(0);
// 根据课程所属的学科ID通过学科服务查询并获取对应的学科实体信息
SubjectEntity subject = iSubjectService.find(course.getSubjectId()); SubjectEntity subject = iSubjectService.find(course.getSubjectId());
// 通过章节服务,查询并获取当前课程对应的章节列表信息,用于在课程详情页面展示课程包含的章节内容等
List<ChapterEntity> chapterList = iChapterService.findCourseChapter(book.getUid()); List<ChapterEntity> chapterList = iChapterService.findCourseChapter(book.getUid());
// 通过课程服务,查找与当前课程属于同一学科的其他课程列表,可能用于相关课程推荐等场景
List<CourseEntity> courseList = iCourseService.findBySubjectId(course.getSubjectId()); List<CourseEntity> courseList = iCourseService.findBySubjectId(course.getSubjectId());
// 将查询到的课程章节列表信息添加到ModelAndView对象中以便在视图中展示课程的章节情况
model.addObject("chapterList", chapterList); model.addObject("chapterList", chapterList);
// 将查询到的同学科下的其他课程列表信息添加到ModelAndView对象中以便在视图中展示相关课程推荐等内容
model.addObject("courseList", courseList); model.addObject("courseList", courseList);
// 将查询到的课程实体信息添加到ModelAndView对象中以便在视图中展示课程的详细信息
model.addObject("course", course); model.addObject("course", course);
// 将查询到的学科实体信息添加到ModelAndView对象中以便在视图中展示课程所属学科等相关信息
model.addObject("subject", subject); model.addObject("subject", subject);
// 将查询到的课程相关的书籍列表信息添加到ModelAndView对象中以便在视图中展示课程包含的书籍等内容
model.addObject("bookList", bookList); model.addObject("bookList", bookList);
model.addObject("book" , book);
// 将获取到的首本图书实体信息添加到ModelAndView对象中具体用途根据业务而定
model.addObject("book", book);
// 设置视图名称为"chapter",对应相应的课程详情页面模板,后续会根据这个名称去查找并渲染对应的视图
model.setViewName("chapter"); model.setViewName("chapter");
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户
return model; return model;
} catch (Exception e) { } catch (Exception e) {
// 如果在查询相关数据过程中出现异常,将视图名称设置为"404",通常用于表示请求的资源不存在等错误情况
model.setViewName("404"); model.setViewName("404");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出404页面展示给用户
return model; return model;
} }
} }
/**
* IDGETID
* 便使
*
* @param courseId
* @return List<ChapterEntity>
*/
@RequestMapping(value = {"course/findChapter"}, method = RequestMethod.GET) @RequestMapping(value = {"course/findChapter"}, 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);
} }
/**
* IDGETID
* Result便
*
* @param courseId
* @return ResultResult
*/
@RequestMapping(value = {"course/findChapterTreeByCourseId"}, method = RequestMethod.GET) @RequestMapping(value = {"course/findChapterTreeByCourseId"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result findChapterTreeByCourseId(String courseId){ public Result findChapterTreeByCourseId(String courseId) {
return Result.successResult(iChapterService.getChapterTree(courseId)); return Result.successResult(iChapterService.getChapterTree(courseId));
} }

@ -24,72 +24,113 @@ import com.tamguo.util.DateUtils;
/** /**
* *
* Spring
*/ */
@RestController @RestController
public class FileUploadController { public class FileUploadController {
// 创建日志记录器,用于记录文件上传过程中的关键信息,例如文件保存的位置、上传是否成功等情况,方便后续查看日志进行调试和问题排查
private Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); private Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
// 通过配置文件注入文件存储路径属性值,指定了服务器上用于存放上传文件的基础路径,后续会根据日期等规则在此基础上构建具体的文件存储目录
@Value("${file.storage.path}") @Value("${file.storage.path}")
private String fileStoragePath; private String fileStoragePath;
// 自动注入CacheService用于缓存相关操作在这里主要是借助缓存来生成具有唯一性且按一定规则递增的文件编号文件名的一部分
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
// 定义文件编号的默认格式化字符串,用于格式化生成的文件编号,保证编号格式统一,使其具有固定的长度和格式要求
private static final String FILES_NO_FORMAT = "00000"; private static final String FILES_NO_FORMAT = "00000";
// 定义文件编号的前缀,方便识别文件相关的编号,使其在整体文件名中有一定的标识性,便于区分不同类型或用途的文件
private static final String FILES_PREFIX = "FP"; private static final String FILES_PREFIX = "FP";
/**
* POSTMultipartFileUeditor
* Ueditor
*
* @param upfile MultipartFile
* @return UeditorSUCCESSERROR访
* @throws IOException I/O
*/
@RequestMapping(value = "/uploadFile", method = RequestMethod.POST) @RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Ueditor imgUpload(MultipartFile upfile) throws IOException { public Ueditor imgUpload(MultipartFile upfile) throws IOException {
if (!upfile.isEmpty()) { // 判断上传的文件是否为空如果为空则直接返回表示文件为空的错误信息的Ueditor对象给前端
if (!upfile.isEmpty()) {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
// 根据当前日期构建文件存储的路径将配置的基础存储路径与格式化后的当前日期格式为yyyyMMdd拼接起来方便按日期分类存储文件便于管理和查找
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);
// 如果目录不存在,则创建相应的目录,确保文件有存储的位置,避免后续保存文件时因目录不存在而出现错误
if (!dir.exists()) if (!dir.exists())
dir.mkdirs(); dir.mkdirs();
// 生成文件名,结合通过缓存获取的文件编号以及原文件名的后缀组成完整的文件名,保证文件名的唯一性以及符合一定的命名规则
String fileName = this.getTeacherNo() + upfile.getOriginalFilename().substring(upfile.getOriginalFilename().lastIndexOf(".")); String fileName = this.getTeacherNo() + upfile.getOriginalFilename().substring(upfile.getOriginalFilename().lastIndexOf("."));
// 创建表示服务器上要保存的文件的File对象指定其完整的保存路径和文件名
File serverFile = new File(dir + File.separator + fileName); File serverFile = new File(dir + File.separator + fileName);
// 获取文件输入流,用于读取上传文件的内容,以便后续将内容写入到服务器的文件中
in = upfile.getInputStream(); in = upfile.getInputStream();
// 创建文件输出流用于将读取的文件内容写入到服务器指定的存储位置即上面创建的serverFile所代表的文件中
out = new FileOutputStream(serverFile); out = new FileOutputStream(serverFile);
byte[] b = new byte[1024]; byte[] b = new byte[1024];
int len = 0; int len = 0;
// 通过循环读取输入流中的文件内容,并写入到输出流中,实现文件的复制保存操作,将上传的文件从临时存储位置复制到服务器指定的存储位置
while ((len = in.read(b)) > 0) { while ((len = in.read(b)) > 0) {
out.write(b, 0, len); out.write(b, 0, len);
} }
// 关闭输出流,释放相关资源,避免资源泄露
out.close(); out.close();
// 关闭输入流,释放相关资源,避免资源泄露
in.close(); in.close();
// 记录文件在服务器上的存储位置信息到日志中,方便后续查看和排查问题,例如确认文件是否保存成功以及保存的具体位置是否正确等
logger.info("Server File Location=" + serverFile.getAbsolutePath()); logger.info("Server File Location=" + serverFile.getAbsolutePath());
Ueditor ueditor = new Ueditor(); // 创建表示文件上传成功的Ueditor对象并设置相应的成功状态、原文件名作为标题以及文件在服务器上的访问路径等信息
ueditor.setState("SUCCESS"); Ueditor ueditor = new Ueditor();
ueditor.setTitle(upfile.getOriginalFilename()); ueditor.setState("SUCCESS");
ueditor.setUrl("files" + "/" +DateUtils.format(new Date(), "yyyyMMdd") + "/" + fileName); ueditor.setTitle(upfile.getOriginalFilename());
return ueditor; ueditor.setUrl("files" + "/" + DateUtils.format(new Date(), "yyyyMMdd") + "/" + fileName);
return ueditor;
} catch (Exception e) { } catch (Exception e) {
// 如果在文件上传过程中出现异常创建表示文件上传失败的Ueditor对象并设置相应的错误状态和提示信息这里统一设置为"上传失败"),然后返回该对象告知前端上传失败
Ueditor ueditor = new Ueditor(); Ueditor ueditor = new Ueditor();
ueditor.setState("ERROR"); ueditor.setState("ERROR");
ueditor.setTitle("上传失败"); ueditor.setTitle("上传失败");
return ueditor; return ueditor;
} finally { } finally {
if (out != null) { // 在最终块中确保输出流资源被正确关闭,避免资源泄露,即使在前面出现异常的情况下也能保证资源的正确释放
if (out!= null) {
out.close(); out.close();
out = null; out = null;
} }
if (in != null) { // 在最终块中确保输入流资源被正确关闭,避免资源泄露,即使在前面出现异常的情况下也能保证资源的正确释放
if (in!= null) {
in.close(); in.close();
in = null; in = null;
} }
} }
} else { } else {
// 如果上传的文件为空创建表示文件为空的错误的Ueditor对象并设置相应的错误状态和提示信息然后返回该对象告知前端文件为空
Ueditor ueditor = new Ueditor(); Ueditor ueditor = new Ueditor();
ueditor.setState("ERROR"); ueditor.setState("ERROR");
ueditor.setTitle("File is empty"); ueditor.setTitle("File is empty");
return ueditor; return ueditor;
} }
} }
/**
*
* 便
*
* @return
*/
private String getTeacherNo() { private String getTeacherNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
String format = sdf.format(new Date()); String format = sdf.format(new Date());

@ -5,36 +5,71 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.ModelAndView;
// 标识这是一个Spring的控制器类用于处理不同路径的Web请求并返回相应的视图给客户端展示不同的页面内容。
@Controller @Controller
public class IndexController { public class IndexController {
@RequestMapping(value = "/", method = RequestMethod.GET) /**
* "/"GET"index""index"JSPThymeleaf
*
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@RequestMapping(value = "/", method = RequestMethod.GET)
public ModelAndView indexAction(ModelAndView model) { public ModelAndView indexAction(ModelAndView model) {
model.setViewName("index"); model.setViewName("index");
return model; return model;
} }
/**
* "/index"GET"index"访使
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@RequestMapping(value = "/index", method = RequestMethod.GET) @RequestMapping(value = "/index", method = RequestMethod.GET)
public ModelAndView mainAction(ModelAndView model) { public ModelAndView mainAction(ModelAndView model) {
model.setViewName("index"); model.setViewName("index");
return model; return model;
} }
/**
* "/baidu_verify_5agfTbCO3Q"GET"thirdparty/baidu_verify_5agfTbCO3Q"
*
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@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"
*
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@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"
*
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@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;
} }
} }

@ -27,94 +27,176 @@ import com.google.code.kaptcha.Producer;
import com.tamguo.util.Result; import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils; import com.tamguo.util.ShiroUtils;
// 标识这是一个Spring的控制器类主要用于处理用户登录相关的Web请求包括生成验证码、展示登录页面以及处理登录提交等操作。
@Controller @Controller
public class LoginController { public class LoginController {
// 自动注入Producer用于生成验证码相关的操作比如创建验证码文本和对应的图片验证码。
@Autowired @Autowired
private Producer producer; private Producer producer;
/**
* "captcha.jpg"
* JPEG
* Shiro便
*
* @param response HttpServletResponse便
* @throws ServletException Servlet
* @throws IOException I/O
*/
@RequestMapping("captcha.jpg") @RequestMapping("captcha.jpg")
public void captcha(HttpServletResponse response) throws ServletException, IOException { public void captcha(HttpServletResponse response) throws ServletException, IOException {
// 设置响应头,禁止客户端缓存验证码图片,确保每次获取的验证码都是新生成的。
response.setHeader("Cache-Control", "no-store, no-cache"); response.setHeader("Cache-Control", "no-store, no-cache");
// 设置响应的内容类型为JPEG格式的图片告知客户端返回的数据是图片类型以便正确解析和显示。
response.setContentType("image/jpeg"); response.setContentType("image/jpeg");
// 生成文字验证码
// 生成验证码的文本内容通常是由数字、字母等组成的随机字符串具体生成规则由所使用的验证码生成组件这里是Producer决定。
String text = producer.createText(); String text = producer.createText();
// 生成图片验证码 // 根据生成的验证码文本创建对应的图片验证码生成的是一个BufferedImage类型的图片对象包含了验证码文本的可视化表示形式。
BufferedImage image = producer.createImage(text); BufferedImage image = producer.createImage(text);
// 保存到shiro session
// 将生成的验证码文本保存到Shiro的会话中以Constants.KAPTCHA_SESSION_KEY作为键方便后续在验证用户输入的验证码时从会话中获取并比对。
ShiroUtils.setSessionAttribute(Constants.KAPTCHA_SESSION_KEY, text); ShiroUtils.setSessionAttribute(Constants.KAPTCHA_SESSION_KEY, text);
// 获取ServletResponse的输出流用于将生成的图片数据写入到响应中发送给客户端进行显示。
ServletOutputStream out = response.getOutputStream(); ServletOutputStream out = response.getOutputStream();
// 使用ImageIO将BufferedImage格式的图片以JPEG格式写入到输出流中实现将验证码图片返回给客户端的功能。
ImageIO.write(image, "jpg", out); ImageIO.write(image, "jpg", out);
} }
/**
* GET"login""login"JSPThymeleaf
* "isVerifyCode""0"
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@RequestMapping(value = "/login", method = RequestMethod.GET) @RequestMapping(value = "/login", 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;
} }
/**
* POSTShiro
*
*
* @param username
* @param password
* @param verifyCode
* @param model ModelAndView便
* @param session HttpSession
* @param response HttpServletResponse
* @throws IOException I/O
*/
@RequestMapping(value = "/submitLogin", method = RequestMethod.POST) @RequestMapping(value = "/submitLogin", 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.successResult(null); Result result = Result.successResult(null);
if(StringUtils.isEmpty(verifyCode)) {
// 判断用户输入的验证码是否为空如果为空则表示用户未输入验证码设置相应的错误提示信息到Result对象中。
if (StringUtils.isEmpty(verifyCode)) {
result = Result.result(202, null, "请输入验证码"); result = Result.result(202, null, "请输入验证码");
} else if(StringUtils.isNotEmpty(verifyCode)){ } else if (StringUtils.isNotEmpty(verifyCode)) {
// 如果用户输入了验证码从Shiro会话中获取之前生成并保存的验证码文本用于和用户输入的验证码进行比对验证。
String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY); String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);
// 忽略大小写比较用户输入的验证码和会话中的验证码是否一致如果不一致则表示验证码错误设置相应的错误提示信息到Result对象中。
if (!verifyCode.equalsIgnoreCase(kaptcha)) { if (!verifyCode.equalsIgnoreCase(kaptcha)) {
result = Result.result(205, null, "验证码错误"); result = Result.result(205, null, "验证码错误");
} else { } else {
// 如果验证码验证通过获取Shiro的Subject对象它代表了当前执行的用户主体用于后续进行登录认证等操作。
Subject subject = ShiroUtils.getSubject(); Subject subject = ShiroUtils.getSubject();
// 创建一个UsernamePasswordToken对象将用户输入的用户名和密码封装进去作为登录认证的凭证传递给Shiro框架。
UsernamePasswordToken token = new UsernamePasswordToken(username, password); UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try { try {
// 通过Subject对象发起登录认证操作Shiro框架会根据配置的认证逻辑如从数据库查询用户信息并比对密码等进行验证。
subject.login(token); subject.login(token);
// 如果登录成功将登录后的用户信息保存到HttpSession中方便在后续的请求处理中获取当前登录用户的相关信息键为"currMember"。
session.setAttribute("currMember", ShiroUtils.getMember()); session.setAttribute("currMember", ShiroUtils.getMember());
// 重定向到登录成功后的页面(这里是"member/index.html",可能是会员相关的首页之类的页面,具体根据业务需求确定)。
response.sendRedirect("member/index.html"); response.sendRedirect("member/index.html");
// 登录成功后直接返回null因为已经进行了重定向操作不需要再返回视图相关的内容了。
return null; return null;
} catch (UnknownAccountException e) { } catch (UnknownAccountException e) {
// 如果Shiro认证过程中抛出UnknownAccountException异常表示用户名不存在或者未找到对应的用户信息设置相应的错误提示信息到Result对象中。
result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码"); result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码");
} catch (IncorrectCredentialsException e) { } catch (IncorrectCredentialsException e) {
// 如果Shiro认证过程中抛出IncorrectCredentialsException异常表示密码错误设置相应的错误提示信息到Result对象中。
result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码"); result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码");
} catch (LockedAccountException e) { } catch (LockedAccountException e) {
// 如果Shiro认证过程中抛出LockedAccountException异常表示账号被锁定设置相应的错误提示信息到Result对象中。
result = Result.result(203, null, "账号被锁定"); result = Result.result(203, null, "账号被锁定");
} }
} }
} }
// 如果登录验证未通过(出现各种错误情况),设置视图名称为"login",表示返回登录页面展示给用户相应的错误提示信息。
model.setViewName("login"); 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;
} }
/**
* GET
* Result
* submitLoginShiroResponseBodyResult
*
* @param username
* @param password
* @param captcha
* @param model ModelAndView使
* @param session HttpSession
* @return Result
*/
@RequestMapping(value = "/miniLogin", method = RequestMethod.GET) @RequestMapping(value = "/miniLogin", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result miniLogin(String username , String password , String captcha, ModelAndView model , HttpSession session) { public Result miniLogin(String username, String password, String captcha, ModelAndView model,
HttpSession session) {
Result result = null; Result result = null;
if(StringUtils.isEmpty(captcha)) { // 判断用户输入的验证码是否为空如果为空则表示用户未输入验证码设置相应的错误提示信息到Result对象中。
if (StringUtils.isEmpty(captcha)) {
result = Result.result(204, null, "请输入验证码"); result = Result.result(204, null, "请输入验证码");
} else if(StringUtils.isNotEmpty(captcha)){ } else if (StringUtils.isNotEmpty(captcha)) {
// 如果用户输入了验证码从Shiro会话中获取之前生成并保存的验证码文本用于和用户输入的验证码进行比对验证。
String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY); String kaptcha = ShiroUtils.getKaptcha(Constants.KAPTCHA_SESSION_KEY);
// 忽略大小写比较用户输入的验证码和会话中的验证码是否一致如果不一致则表示验证码错误设置相应的错误提示信息到Result对象中。
if (!captcha.equalsIgnoreCase(kaptcha)) { if (!captcha.equalsIgnoreCase(kaptcha)) {
result = Result.result(205, null, "验证码错误"); result = Result.result(205, null, "验证码错误");
}else { } else {
// 如果验证码验证通过获取Shiro的Subject对象它代表了当前执行的用户主体用于后续进行登录认证等操作。
Subject subject = ShiroUtils.getSubject(); Subject subject = ShiroUtils.getSubject();
// 创建一个UsernamePasswordToken对象将用户输入的用户名和密码封装进去作为登录认证的凭证传递给Shiro框架。
UsernamePasswordToken token = new UsernamePasswordToken(username, password); UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try { try {
// 通过Subject对象发起登录认证操作Shiro框架会根据配置的认证逻辑如从数据库查询用户信息并比对密码等进行验证。
subject.login(token); subject.login(token);
// 如果登录成功将登录后的用户信息保存到HttpSession中方便在后续的请求处理中获取当前登录用户的相关信息键为"currMember"。
session.setAttribute("currMember", ShiroUtils.getMember()); session.setAttribute("currMember", ShiroUtils.getMember());
// 创建表示登录成功的Result对象并将登录后的用户信息设置到Result对象中以便返回给客户端。
result = Result.successResult(ShiroUtils.getMember()); result = Result.successResult(ShiroUtils.getMember());
} catch (UnknownAccountException e) { } catch (UnknownAccountException e) {
// 如果Shiro认证过程中抛出UnknownAccountException异常表示用户名不存在或者未找到对应的用户信息设置相应的错误提示信息到Result对象中。
result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码"); result = Result.result(201, null, "用户名或密码有误,请重新输入或找回密码");
} catch (IncorrectCredentialsException e) { } catch (IncorrectCredentialsException e) {
// 如果Shiro认证过程中抛出IncorrectCredentialsException异常表示密码错误设置相应的错误提示信息到Result对象中。
result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码"); result = Result.result(202, null, "用户名或密码有误,请重新输入或找回密码");
} catch (LockedAccountException e) { } catch (LockedAccountException e) {
// 如果Shiro认证过程中抛出LockedAccountException异常表示账号被锁定设置相应的错误提示信息到Result对象中。
result = Result.result(203, null, "账号被锁定"); result = Result.result(203, null, "账号被锁定");
} }
} }
} }
return result; return result;
} }
} }

@ -25,84 +25,191 @@ import com.tamguo.util.TamguoConstant;
/** /**
* Controller - * Controller -
* SpringWeb
* ModelAndViewJSON@ResponseBody
* *
* @author candy.tam * @author candy.tam
*
*/ */
@Controller @Controller
public class PaperController { public class PaperController {
// 自动注入ICourseService用于处理课程相关的业务逻辑比如根据课程ID查找课程、按学科ID查找课程列表等操作在获取试卷相关信息时可能会涉及课程信息的查询。
@Autowired @Autowired
private ICourseService iCourseService; private ICourseService iCourseService;
// 自动注入IAreaService用于处理地区相关的业务逻辑例如查找根地区列表等操作可能在按地区筛选试卷等功能中会用到地区相关信息的查询。
@Autowired @Autowired
private IAreaService iAreaService; private IAreaService iAreaService;
// 自动注入IPaperService用于处理试卷相关的业务逻辑像查找试卷、获取试卷列表、根据各种条件筛选试卷、获取推荐试卷等操作都是通过该服务来完成。
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
// 自动注入IQuestionService用于处理试题相关的业务逻辑例如查找试卷对应的试题列表等操作在试卷详情页面展示试题信息时会调用该服务。
@Autowired @Autowired
private IQuestionService iQuestionService; private IQuestionService iQuestionService;
// 自动注入ISubjectService用于处理学科相关的业务逻辑例如根据学科ID查找学科信息等操作试卷通常与特定学科相关查询试卷相关信息时可能需要获取学科相关内容。
@Autowired @Autowired
private ISubjectService iSubjectService; private ISubjectService iSubjectService;
/**
* GETIDID
* ModelAndView"paperlist"ModelAndView
* "404"ModelAndView404
*
* @param subjectId
* @param courseId
* @param paperType
* @param year 便
* @param area
* @param pageNum
* @param model ModelAndView
* @return ModelAndView
*/
@RequestMapping(value = {"paperlist/{subjectId}-{courseId}-{paperType}-{year}-{area}-{pageNum}"}, method = RequestMethod.GET) @RequestMapping(value = {"paperlist/{subjectId}-{courseId}-{paperType}-{year}-{area}-{pageNum}"}, method = RequestMethod.GET)
public ModelAndView indexAction(@PathVariable String subjectId , @PathVariable String courseId , @PathVariable String paperType, public ModelAndView indexAction(@PathVariable String subjectId, @PathVariable String courseId,
@PathVariable String year , @PathVariable String area , @PathVariable Integer pageNum, ModelAndView model) { @PathVariable String paperType, @PathVariable String year, @PathVariable String area,
try { @PathVariable Integer pageNum, ModelAndView model) {
model.setViewName("paperlist"); try {
// 设置视图名称为"paperlist",对应相应的试卷列表页面模板,后续会根据这个名称去查找并渲染对应的视图展示给用户。
CourseEntity course = iCourseService.find(courseId); model.setViewName("paperlist");
List<CourseEntity> courseList = iCourseService.findBySubjectId(subjectId);
SubjectEntity subject = iSubjectService.find(subjectId); // 根据传入的课程ID通过课程服务查询并获取对应的课程实体信息用于在试卷列表页面展示试卷所属课程等相关信息。
List<AreaEntity> areaList = iAreaService.findRootArea(); CourseEntity course = iCourseService.find(courseId);
PageUtils page = PageUtils.getPage(iPaperService.findList(subjectId , courseId , paperType , year , area , pageNum));
if(course == null) { // 通过课程服务查找与传入学科ID对应的所有课程列表可能用于在试卷列表页面展示该学科下的所有课程方便用户切换查看不同课程的试卷情况。
course = courseList.get(0); List<CourseEntity> courseList = iCourseService.findBySubjectId(subjectId);
}
Long total = iPaperService.getPaperTotal(); // 根据传入的学科ID通过学科服务查询并获取对应的学科实体信息用于在试卷列表页面展示试卷所属学科等相关信息。
model.addObject("courseList", courseList); SubjectEntity subject = iSubjectService.find(subjectId);
model.addObject("subject", subject);
model.addObject("course", course); // 通过地区服务查找根地区列表信息,可能用于在试卷列表页面提供地区筛选的下拉菜单等功能,展示所有可选的地区范围(具体根据业务需求而定)。
model.addObject("areaList", areaList); List<AreaEntity> areaList = iAreaService.findRootArea();
model.addObject("paperPage" , page);
model.addObject("total" , total); // 调用试卷服务的findList方法根据传入的多个条件学科ID、课程ID、试卷类型、年份、地区、页码获取相应的试卷列表数据并通过PageUtils工具类进行分页相关的处理得到包含分页信息的PageUtils对象。
model.addObject("courseId", course.getUid()); PageUtils page = PageUtils.getPage(iPaperService.findList(subjectId, courseId, paperType, year, area, pageNum));
model.addObject("paperType", paperType);
model.addObject("year", year); // 如果通过课程ID未查询到对应的课程实体可能课程ID不存在或者其他原因导致查询失败则取课程列表中的第一个课程作为默认展示具体根据业务逻辑需求调整
model.addObject("area", area); if (course == null) {
return model; course = courseList.get(0);
}
// 调用试卷服务的getPaperTotal方法获取试卷的总数量可能用于在试卷列表页面展示总共有多少试卷等相关统计信息。
Long total = iPaperService.getPaperTotal();
// 将查询到的课程列表信息添加到ModelAndView对象中以便在视图中展示该学科下的所有课程情况方便用户切换查看不同课程的试卷。
model.addObject("courseList", courseList);
// 将查询到的学科实体信息添加到ModelAndView对象中以便在视图中展示试卷所属学科等相关信息。
model.addObject("subject", subject);
// 将查询到的课程实体信息添加到ModelAndView对象中以便在视图中展示试卷所属课程等相关信息。
model.addObject("course", course);
// 将查询到的地区列表信息添加到ModelAndView对象中以便在视图中展示可筛选的地区范围供用户按地区查找试卷。
model.addObject("areaList", areaList);
// 将包含分页信息的试卷列表数据通过PageUtils包装添加到ModelAndView对象中以便在视图中展示具体的试卷列表内容。
model.addObject("paperPage", page);
// 将试卷的总数量添加到ModelAndView对象中以便在视图中展示试卷的统计信息如告知用户总共有多少试卷等。
model.addObject("total", total);
// 将当前试卷所属的课程ID添加到ModelAndView对象中可能用于前端页面的一些交互逻辑或者链接生成等用途具体根据前端需求确定
model.addObject("courseId", course.getUid());
// 将当前试卷的类型添加到ModelAndView对象中可能用于在视图中对试卷进行分类展示或者筛选等相关操作具体根据前端展示需求确定
model.addObject("paperType", paperType);
// 将当前试卷对应的年份添加到ModelAndView对象中可能用于在视图中按年份对试卷进行筛选或者展示等相关操作具体根据前端展示需求确定
model.addObject("year", year);
// 将当前试卷所属的地区添加到ModelAndView对象中可能用于在视图中按地区对试卷进行筛选或者展示等相关操作具体根据前端展示需求确定
model.addObject("area", area);
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户试卷列表页面内容。
return model;
} catch (Exception e) { } catch (Exception e) {
// 如果在查询相关数据过程中出现异常,将视图名称设置为"404",通常用于表示请求的资源不存在等错误情况。
model.setViewName("404"); model.setViewName("404");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出404页面展示给用户。
return model; return model;
} }
} }
/**
* GETID
* ModelAndView"paper"ModelAndView
* "404"ModelAndView404
*
* @param paperId
* @param model ModelAndView
* @return ModelAndView
*/
@RequestMapping(value = {"/paper/{paperId}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"/paper/{paperId}.html"}, method = RequestMethod.GET)
public ModelAndView indexAction(@PathVariable String paperId , ModelAndView model){ public ModelAndView indexAction(@PathVariable String paperId, ModelAndView model) {
try { try {
// 设置视图名称为"paper",对应相应的试卷详情页面模板,后续会根据这个名称去查找并渲染对应的视图展示给用户。
model.setViewName("paper"); model.setViewName("paper");
// 根据传入的试卷ID通过试卷服务查询并获取对应的试卷实体信息用于在试卷详情页面展示试卷的详细内容。
PaperEntity paper = iPaperService.find(paperId); PaperEntity paper = iPaperService.find(paperId);
// 将查询到的试卷实体信息添加到ModelAndView对象中以便在视图中展示试卷的详细内容如试卷名称、考试时间等信息。
model.addObject("paper", paper); model.addObject("paper", paper);
model.addObject("subject", StringUtils.isEmpty(paper.getSubjectId()) ? null : iSubjectService.find(paper.getSubjectId()));
model.addObject("course", StringUtils.isEmpty(paper.getCourseId()) ? null : iCourseService.find(paper.getCourseId())); // 判断试卷的学科ID是否为空如果不为空则通过学科服务查询并获取对应的学科实体信息然后添加到ModelAndView对象中用于在试卷详情页面展示试卷所属学科等相关信息
// 如果学科ID为空则添加null表示没有对应的学科信息这种情况可能是数据异常等原因导致具体根据业务逻辑处理
model.addObject("subject", StringUtils.isEmpty(paper.getSubjectId())? null : iSubjectService.find(paper.getSubjectId()));
// 判断试卷的课程ID是否为空如果不为空则通过课程服务查询并获取对应的课程实体信息然后添加到ModelAndView对象中用于在试卷详情页面展示试卷所属课程等相关信息
// 如果课程ID为空则添加null表示没有对应的课程信息这种情况可能是数据异常等原因导致具体根据业务逻辑处理
model.addObject("course", StringUtils.isEmpty(paper.getCourseId())? null : iCourseService.find(paper.getCourseId()));
// 通过试题服务,查找该试卷对应的试题列表信息,用于在试卷详情页面展示试卷包含的具体试题内容等情况。
model.addObject("questionList", iQuestionService.findPaperQuestion(paperId)); model.addObject("questionList", iQuestionService.findPaperQuestion(paperId));
// 获取推荐试卷 // 获取真题推荐试卷列表调用试卷服务的featuredPaper方法传入特定的真题标识TamguoConstant.ZHENGTI_PAPER_ID和当前试卷的学科ID获取相关的真题推荐试卷列表
// 并添加到ModelAndView对象中用于在试卷详情页面展示相关的推荐试卷内容方便用户查看其他类似的真题试卷。
model.addObject("zhentiPaperList", iPaperService.featuredPaper(TamguoConstant.ZHENGTI_PAPER_ID, paper.getSubjectId())); model.addObject("zhentiPaperList", iPaperService.featuredPaper(TamguoConstant.ZHENGTI_PAPER_ID, paper.getSubjectId()));
// 获取模拟题推荐试卷列表调用试卷服务的featuredPaper方法传入特定的模拟题标识TamguoConstant.MONI_PAPER_ID和当前试卷的学科ID获取相关的模拟题推荐试卷列表
// 并添加到ModelAndView对象中用于在试卷详情页面展示相关的推荐试卷内容方便用户查看其他类似的模拟题试卷。
model.addObject("moniPaperList", iPaperService.featuredPaper(TamguoConstant.MONI_PAPER_ID, paper.getSubjectId())); model.addObject("moniPaperList", iPaperService.featuredPaper(TamguoConstant.MONI_PAPER_ID, paper.getSubjectId()));
// 获取押题推荐试卷列表调用试卷服务的featuredPaper方法传入特定的押题标识TamguoConstant.YATI_PAPER_ID和当前试卷的学科ID获取相关的押题推荐试卷列表
// 并添加到ModelAndView对象中用于在试卷详情页面展示相关的推荐试卷内容方便用户查看其他类似的押题试卷。
model.addObject("yatiPaperList", iPaperService.featuredPaper(TamguoConstant.YATI_PAPER_ID, paper.getSubjectId())); model.addObject("yatiPaperList", iPaperService.featuredPaper(TamguoConstant.YATI_PAPER_ID, paper.getSubjectId()));
// 获取热门试卷列表调用试卷服务的findHotPaper方法传入当前试卷的学科ID和课程ID获取相关的热门试卷列表
// 并添加到ModelAndView对象中用于在试卷详情页面展示相关的推荐试卷内容方便用户查看其他热门的试卷。
model.addObject("hotPaperList", iPaperService.findHotPaper(paper.getSubjectId(), paper.getCourseId())); model.addObject("hotPaperList", iPaperService.findHotPaper(paper.getSubjectId(), paper.getCourseId()));
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户试卷详情页面内容。
return model; return model;
} catch (Exception e) { } catch (Exception e) {
// 如果在查询相关数据过程中出现异常,将视图名称设置为"404",通常用于表示请求的资源不存在等错误情况。
model.setViewName("404"); model.setViewName("404");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出404页面展示给用户。
return model; return model;
} }
} }
/**
* IDGETIDJSON
* 便
*
* @param areaId
* @param type
* @return List<PaperEntity>
*/
@RequestMapping(value = {"/paper/area/{areaId}-{type}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"/paper/area/{areaId}-{type}.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public List<PaperEntity> findPaperByAreaId(@PathVariable String areaId ,@PathVariable String type){ public List<PaperEntity> findPaperByAreaId(@PathVariable String areaId, @PathVariable String type) {
return iPaperService.findPaperByAreaId(areaId , type); return iPaperService.findPaperByAreaId(areaId, type);
} }
} }

@ -10,65 +10,146 @@ import org.springframework.web.servlet.ModelAndView;
import com.tamguo.service.IMemberService; import com.tamguo.service.IMemberService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
// 标识这是一个Spring的控制器类主要用于处理与用户密码相关的各种操作的Web请求例如确认账号、安全验证、重置密码以及检查账号是否存在等功能。
@Controller @Controller
public class PasswordController { public class PasswordController {
// 自动注入IMemberService用于调用会员相关的业务逻辑方法比如确认账号、进行安全检查、重置密码以及检查账号有效性等操作这些操作都依赖于会员服务层提供的具体实现。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/**
* GET"password/confirmAccount""password/confirmAccount"JSPThymeleaf
*
*
* @param model ModelAndView
* @return ModelAndView便Spring
*/
@RequestMapping(value = "password/find", method = RequestMethod.GET) @RequestMapping(value = "password/find", 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;
} }
/**
* POSTusernameveritycodeconfirmAccount
* Resultcode
*
* @param username
* @param veritycode
* @param model ModelAndView便
* @return ModelAndView便Spring
*/
@RequestMapping(value = "password/confirmAccount", method = RequestMethod.POST) @RequestMapping(value = "password/confirmAccount", method = RequestMethod.POST)
public ModelAndView submitConfirmAccount(String username , String veritycode , ModelAndView model){ public ModelAndView submitConfirmAccount(String username, String veritycode, ModelAndView model) {
// 调用会员服务的confirmAccount方法传入用户名和验证码进行账号确认操作返回包含操作结果信息的Result对象其中包含状态码、提示信息以及可能的业务数据等内容。
Result result = iMemberService.confirmAccount(username, veritycode); Result result = iMemberService.confirmAccount(username, veritycode);
if(result.getCode() == 200){
// 判断返回的Result对象中的状态码是否为200表示账号确认操作成功通常意味着账号存在且验证码匹配等情况符合预期。
if (result.getCode() == 200) {
// 如果账号确认成功,设置视图名称为"password/securityCheck",表示要跳转到密码找回流程中的安全检查页面,用于后续进一步验证用户身份等操作。
model.setViewName("password/securityCheck"); model.setViewName("password/securityCheck");
// 将确认账号操作返回的Result对象添加到视图模型中可能在安全检查页面会展示一些相关的提示信息或者使用其中的数据具体根据前端页面需求确定
model.addObject("result", result); model.addObject("result", result);
model.addObject("isEmail", username.contains("@") ? "1" : "0"); // 根据用户名中是否包含"@"符号来判断是邮箱账号还是手机号等其他形式账号,将判断结果("1"表示邮箱账号,"0"表示非邮箱账号)添加到视图模型中,
}else{ // 可能用于安全检查页面根据账号类型进行不同的验证方式展示等逻辑处理(例如邮箱账号可能通过邮件验证,手机号账号可能通过短信验证等,具体根据业务逻辑确定)。
model.addObject("isEmail", username.contains("@")? "1" : "0");
} else {
// 如果账号确认失败,设置视图名称为"password/confirmAccount",表示返回确认账号页面,以便用户重新输入正确的信息进行账号确认操作。
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);
// 将操作失败返回的Result对象中的状态码添加到视图模型中方便前端页面根据状态码进行不同的错误提示信息展示等逻辑处理告知用户具体是哪种验证失败的情况。
model.addObject("code", result.getCode()); model.addObject("code", result.getCode());
} }
// 返回包含视图名称和相应数据的ModelAndView对象以便Spring的视图解析器根据视图名称查找并渲染相应的视图展示给用户。
return model; return model;
} }
/**
* POSTusernameisEmailmobileVcodesecurityCheck
* Resultcode
*
* @param username
* @param isEmail "1""0"
* @param mobileVcode 使
* @param model ModelAndView便
* @return ModelAndView便Spring
*/
@RequestMapping(value = "password/securityCheck", method = RequestMethod.POST) @RequestMapping(value = "password/securityCheck", 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); // 调用会员服务的securityCheck方法传入用户名、是否为邮箱账号标识以及手机验证码等参数进行安全检查操作返回包含操作结果信息的Result对象其中包含状态码、提示信息以及可能的业务数据等内容。
if(result.getCode() == 200){ Result result = iMemberService.securityCheck(username, isEmail, mobileVcode);
// 判断返回的Result对象中的状态码是否为200表示安全检查操作成功通常意味着用户身份验证通过等情况符合预期可以进行下一步的密码重置操作。
if (result.getCode() == 200) {
// 将用户名添加到视图模型中,可能在重置密码页面会展示用户名等相关信息,告知用户当前正在重置哪个账号的密码(具体根据前端页面需求确定)。
model.addObject("username", username); model.addObject("username", username);
model.addObject("resetPasswordKey" , result.getResult()); // 将安全检查操作成功后返回的Result对象中包含的用于重置密码的关键信息例如重置密码的令牌等具体根据业务逻辑确定添加到视图模型中
// 后续在重置密码页面会使用该信息来确保是经过安全验证后的合法操作,用于关联和验证密码重置请求。
model.addObject("resetPasswordKey", result.getResult());
// 设置视图名称为"password/resetPassword",表示要跳转到密码找回流程中的重置密码页面,用于用户输入新的密码等操作。
model.setViewName("password/resetPassword"); model.setViewName("password/resetPassword");
}else{ } else {
// 如果安全检查失败将安全检查操作返回的Result对象添加到视图模型中可能在安全检查页面会展示一些相关的错误提示信息或者使用其中的数据具体根据前端页面需求确定
model.addObject("result", result); model.addObject("result", result);
// 将是否为邮箱账号的标识添加到视图模型中,可能用于安全检查页面根据账号类型进行不同的错误提示信息展示等逻辑处理,告知用户具体是哪种账号类型的验证失败情况。
model.addObject("isEmail", isEmail); model.addObject("isEmail", isEmail);
// 添加一个表示验证码错误的标识(值为"1")到视图模型中,方便前端页面根据该标识进行相应的错误提示信息展示,告知用户验证码输入有误等情况(具体根据前端页面展示逻辑确定)。
model.addObject("codeError", "1"); model.addObject("codeError", "1");
// 设置视图名称为"password/securityCheck",表示返回安全检查页面,以便用户重新输入正确的信息进行安全检查操作。
model.setViewName("password/securityCheck"); model.setViewName("password/securityCheck");
} }
// 返回包含视图名称和相应数据的ModelAndView对象以便Spring的视图解析器根据视图名称查找并渲染相应的视图展示给用户。
return model; return model;
} }
/**
* POSTresetPasswordKeyusernamepasswordverifypwd
* resetPasswordResultcode
*
* @param resetPasswordKey
* @param username
* @param password
* @param verifypwd
* @param model ModelAndView便Spring
* @return ModelAndView便Spring
*/
@RequestMapping(value = "password/resetPassword", method = RequestMethod.POST) @RequestMapping(value = "password/resetPassword", 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,
Result result = iMemberService.resetPassword(resetPasswordKey , username , password , verifypwd); ModelAndView model) {
if(result.getCode() == 200){ // 调用会员服务的resetPassword方法传入重置密码关键信息、用户名、新密码以及确认新密码等参数进行密码重置操作返回包含操作结果信息的Result对象其中包含状态码、提示信息以及可能的业务数据等内容。
Result result = iMemberService.resetPassword(resetPasswordKey, username, password, verifypwd);
// 判断返回的Result对象中的状态码是否为200表示密码重置操作成功通常意味着新密码设置成功等情况符合预期。
if (result.getCode() == 200) {
// 如果密码重置成功,设置视图名称为"password/resetPwSuccess",表示要跳转到密码找回流程中的密码重置成功页面,用于告知用户密码重置操作已顺利完成。
model.setViewName("password/resetPwSuccess"); model.setViewName("password/resetPwSuccess");
}else{ } else {
// 如果密码重置失败,设置视图名称为"password/resetPassword",表示返回重置密码页面,以便用户重新输入正确的密码信息进行密码重置操作。
model.setViewName("password/resetPassword"); model.setViewName("password/resetPassword");
} }
// 返回包含视图名称的ModelAndView对象以便Spring的视图解析器根据视图名称查找并渲染相应的视图展示给用户。
return model; return model;
} }
/**
* GETaccountcheckAccountResult
* Result
*
* @param account
* @return Result
*/
@RequestMapping(value = "password/checkAccount", method = RequestMethod.GET) @RequestMapping(value = "password/checkAccount", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result checkAccount(String account){ public Result checkAccount(String account) {
return iMemberService.checkAccount(account); return iMemberService.checkAccount(account);
} }

@ -10,6 +10,7 @@ import org.springframework.web.servlet.ModelAndView;
import com.baomidou.mybatisplus.plugins.Page; import com.baomidou.mybatisplus.plugins.Page;
import com.tamguo.model.ChapterEntity; import com.tamguo.model.ChapterEntity;
import com.baomidou.mybatisplus.plugins.Page;
import com.tamguo.model.CourseEntity; import com.tamguo.model.CourseEntity;
import com.tamguo.model.QuestionEntity; import com.tamguo.model.QuestionEntity;
import com.tamguo.model.SubjectEntity; import com.tamguo.model.SubjectEntity;
@ -20,77 +21,155 @@ import com.tamguo.service.IQuestionService;
import com.tamguo.service.ISubjectService; import com.tamguo.service.ISubjectService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
// 标识这是一个Spring的控制器类主要用于处理与试题相关的各种Web请求例如展示试题列表页面、试题详情页面以及获取特定试题信息等功能
// 通过调用相应的服务层接口获取数据并将数据添加到ModelAndView对象中返回给视图进行展示或者直接以JSON格式返回数据给前端如通过@ResponseBody注解的方法
@Controller @Controller
public class QuestionContrller { public class QuestionContrller {
// 自动注入IQuestionService用于处理试题相关的业务逻辑像根据章节ID获取试题列表、查找普通试题、获取推荐试题、按ID选择试题等操作都是通过该服务来完成。
@Autowired @Autowired
private IQuestionService iQuestionService; private IQuestionService iQuestionService;
// 自动注入IChapterService用于处理章节相关的业务逻辑例如根据章节ID查找章节、查找下一个章节、获取章节所属课程等操作在获取试题相关信息时可能会涉及章节信息的查询。
@Autowired @Autowired
private IChapterService iChapterService; private IChapterService iChapterService;
// 自动注入ISubjectService用于处理学科相关的业务逻辑例如根据课程所属学科ID查找学科信息等操作试题通常与特定学科相关查询试题相关信息时可能需要获取学科相关内容。
@Autowired @Autowired
private ISubjectService iSubjectService; private ISubjectService iSubjectService;
// 自动注入ICourseService用于处理课程相关的业务逻辑比如根据章节所属课程ID查找课程、获取课程所属学科等操作在获取试题相关信息时可能会涉及课程信息的查询。
@Autowired @Autowired
private ICourseService iCourseService; private ICourseService iCourseService;
/**
* GETIDoffsetlimit
*
* ModelAndView"questionList"ModelAndView
* "404"ModelAndView404
*
* @param chapterId
* @param offset
* @param limit
* @param model ModelAndView
* @return ModelAndView
*/
@RequestMapping(value = {"questionlist/{chapterId}-{offset}-{limit}"}, method = RequestMethod.GET) @RequestMapping(value = {"questionlist/{chapterId}-{offset}-{limit}"}, method = RequestMethod.GET)
public ModelAndView questionList(@PathVariable String chapterId , @PathVariable Integer offset , public ModelAndView questionList(@PathVariable String chapterId, @PathVariable Integer offset,
@PathVariable Integer limit , ModelAndView model){ @PathVariable Integer limit, ModelAndView model) {
try { try {
// 设置视图名称为"questionList",对应相应的试题列表页面模板,后续会根据这个名称去查找并渲染对应的视图展示给用户。
model.setViewName("questionList"); model.setViewName("questionList");
// 根据传入的章节ID通过章节服务查询并获取对应的章节实体信息用于在试题列表页面展示试题所属章节等相关信息。
ChapterEntity chapter = iChapterService.findById(chapterId); ChapterEntity chapter = iChapterService.findById(chapterId);
// 通过章节所属的课程ID利用课程服务查询并获取对应的课程实体信息用于在试题列表页面展示试题所属课程等相关信息。
CourseEntity course = iCourseService.find(chapter.getCourseId()); CourseEntity course = iCourseService.find(chapter.getCourseId());
// 通过课程所属的学科ID利用学科服务查询并获取对应的学科实体信息用于在试题列表页面展示试题所属学科等相关信息。
SubjectEntity subject = iSubjectService.find(course.getSubjectId()); SubjectEntity subject = iSubjectService.find(course.getSubjectId());
// 根据章节的父章节ID通过章节服务查询并获取对应的父章节实体信息可能用于在试题列表页面展示章节的层级关系等相关信息具体根据前端展示需求确定
ChapterEntity parentChapter = iChapterService.findById(chapter.getParentId()); ChapterEntity parentChapter = iChapterService.findById(chapter.getParentId());
ChapterEntity nextChapter = iChapterService.findNextPoint(chapter.getParentId() , chapter.getOrders());
// 通过章节服务查找给定父章节下,当前章节顺序之后的下一个章节信息,可能用于在试题列表页面提供章节导航等功能(例如查看下一章的试题等情况,具体根据业务逻辑和前端需求确定)。
ChapterEntity nextChapter = iChapterService.findNextPoint(chapter.getParentId(), chapter.getOrders());
// 创建一个用于分页的Page对象设置当前页码根据传入的偏移量转换而来可能需要根据具体分页逻辑进行计算调整和每页显示的数量。
Page<QuestionEntity> page = new Page<>(); Page<QuestionEntity> page = new Page<>();
page.setCurrent(offset); page.setCurrent(offset);
page.setSize(limit); page.setSize(limit);
Page<QuestionEntity> questionList = iQuestionService.findByChapterId(chapterId , page);
// 调用试题服务的findByChapterId方法根据传入的章节ID和分页对象获取该章节下符合分页条件的试题列表数据返回包含试题列表以及分页相关信息的Page<QuestionEntity>对象。
Page<QuestionEntity> questionList = iQuestionService.findByChapterId(chapterId, page);
// 将查询到的学科实体信息添加到ModelAndView对象中以便在视图中展示试题所属学科等相关信息。
model.addObject("subject", subject); model.addObject("subject", subject);
// 将查询到的课程实体信息添加到ModelAndView对象中以便在视图中展示试题所属课程等相关信息。
model.addObject("course", course); model.addObject("course", course);
// 将查询到的章节实体信息添加到ModelAndView对象中以便在视图中展示试题所属章节等相关信息。
model.addObject("chapter", chapter); model.addObject("chapter", chapter);
model.addObject("parentChapter" , parentChapter);
model.addObject("nextChapter" , nextChapter); // 将查询到的父章节实体信息添加到ModelAndView对象中以便在视图中展示章节的层级关系等相关信息具体根据前端展示需求确定
model.addObject("parentChapter", parentChapter);
// 将查询到的下一个章节实体信息添加到ModelAndView对象中以便在视图中展示章节导航等相关信息例如查看下一章的试题等情况具体根据业务逻辑和前端需求确定
model.addObject("nextChapter", nextChapter);
// 将包含试题列表及分页信息的Page<QuestionEntity>对象添加到ModelAndView对象中以便在视图中展示具体的试题列表内容。
model.addObject("questionList", questionList); model.addObject("questionList", questionList);
// 将课程所属的学科ID添加到ModelAndView对象中可能用于前端页面的一些交互逻辑或者链接生成等用途具体根据前端需求确定
model.addObject("subjectId", course.getSubjectId()); model.addObject("subjectId", course.getSubjectId());
// 将课程的唯一标识符添加到ModelAndView对象中可能用于前端页面的一些交互逻辑或者链接生成等用途具体根据前端需求确定
model.addObject("courseId", course.getUid()); model.addObject("courseId", course.getUid());
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户试题列表页面内容。
return model; return model;
} catch (Exception e) { } catch (Exception e) {
// 如果在查询相关数据过程中出现异常,将视图名称设置为"404",通常用于表示请求的资源不存在等错误情况。
model.setViewName("404"); model.setViewName("404");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出404页面展示给用户。
return model; return model;
} }
} }
/** /**
* 访 * GETuid
* @param uid * ModelAndView"question"ModelAndView
* @param model * "404"ModelAndView404
* @return *
* @param uid
* @param model ModelAndView
* @return ModelAndView
*/ */
@RequestMapping(value = {"/question/{uid}.html"}, method = RequestMethod.GET) @RequestMapping(value = {"/question/{uid}.html"}, method = RequestMethod.GET)
public ModelAndView question(@PathVariable String uid , ModelAndView model){ public ModelAndView question(@PathVariable String uid, ModelAndView model) {
try { try {
// 设置视图名称为"question",对应相应的试题详情页面模板,后续会根据这个名称去查找并渲染对应的视图展示给用户。
model.setViewName("question"); model.setViewName("question");
// 根据传入的试题ID通过试题服务查询并获取对应的试题实体信息用于在试题详情页面展示试题的详细内容。
QuestionEntity question = iQuestionService.findNormalQuestion(uid); QuestionEntity question = iQuestionService.findNormalQuestion(uid);
// 根据试题实体中存储的试题类型代码通过QuestionType枚举类的方法获取对应的试题类型描述信息并设置到试题实体中
// 用于在试题详情页面展示更直观的试题类型名称(而不是原始的类型代码,提升用户体验)。
question.setQuestionType(QuestionType.getQuestionType(question.getQuestionType()).getDesc()); question.setQuestionType(QuestionType.getQuestionType(question.getQuestionType()).getDesc());
// 将查询到的试题实体信息包含更新后的试题类型描述添加到ModelAndView对象中以便在视图中展示试题的详细内容如试题题干、选项等信息。
model.addObject("question", question); model.addObject("question", question);
// 推荐试题 // 获取推荐试题列表调用试题服务的featuredQuestion方法传入当前试题的学科ID和课程ID获取相关的推荐试题列表
model.addObject("featuredQuestionList", iQuestionService.featuredQuestion(question.getSubjectId(),question.getCourseId())); // 并添加到ModelAndView对象中用于在试题详情页面展示相关的推荐试题内容方便用户查看其他类似或相关的试题。
model.addObject("featuredQuestionList", iQuestionService.featuredQuestion(question.getSubjectId(), question.getCourseId()));
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户试题详情页面内容。
return model; return model;
} catch (Exception e) { } catch (Exception e) {
// 如果在查询相关数据过程中出现异常,将视图名称设置为"404",通常用于表示请求的资源不存在等错误情况。
model.setViewName("404"); model.setViewName("404");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出404页面展示给用户。
return model; return model;
} }
} }
/**
* GETuidselect
* Result便使
*
* @param uid
* @param model ModelAndView使
* @return ResultResult
*/
@RequestMapping(value = {"question/getQuestion/{uid}"}, method = RequestMethod.GET) @RequestMapping(value = {"question/getQuestion/{uid}"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result getQuestion(@PathVariable String uid , ModelAndView model){ public Result getQuestion(@PathVariable String uid, ModelAndView model) {
return Result.successResult(iQuestionService.select(uid)); return Result.successResult(iQuestionService.select(uid));
} }

@ -20,50 +20,96 @@ import com.tamguo.service.IMemberService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
import com.tamguo.util.ShiroUtils; import com.tamguo.util.ShiroUtils;
// 标识这是一个Spring的控制器类主要用于处理用户注册相关的Web请求例如展示注册页面、检查用户名和手机号是否可用以及处理实际的注册提交操作等功能。
@Controller @Controller
public class RegisterController { public class RegisterController {
// 自动注入IMemberService用于调用会员相关的业务逻辑方法比如检查用户名是否已存在、检查手机号是否已注册、执行用户注册操作等这些操作都依赖于会员服务层提供的具体实现。
@Autowired @Autowired
private IMemberService iMemberService; private IMemberService iMemberService;
/**
* GET"register""register"JSPThymeleaf
*
*
* @param model ModelAndView
* @param session HttpSession使
* @return ModelAndView便Spring
*/
@RequestMapping(value = "/register", method = RequestMethod.GET) @RequestMapping(value = "/register", 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;
} }
/**
* GETusernamecheckUsername
* ResultResult
*
* @param username 使
* @return Result
*/
@RequestMapping(value = "/checkUsername", method = RequestMethod.GET) @RequestMapping(value = "/checkUsername", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result checkUsername(String username){ public Result checkUsername(String username) {
return iMemberService.checkUsername(username); return iMemberService.checkUsername(username);
} }
/**
* GETmobilecheckMobile
* ResultResult
*
* @param mobile 使
* @return Result
*/
@RequestMapping(value = "/checkMobile", method = RequestMethod.GET) @RequestMapping(value = "/checkMobile", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result checkMobile(String mobile){ public Result checkMobile(String mobile) {
return iMemberService.checkMobile(mobile); return iMemberService.checkMobile(mobile);
} }
/**
* POST@RequestBodyJSONMemberEntityHttpSession
* registerResultcode
* 使ShiroHttpSessionResult
*
* @param member MemberEntityJSON
* @param session HttpSession便
* @return ResultResult200Result
*/
@RequestMapping(value = "/subRegister", method = RequestMethod.POST) @RequestMapping(value = "/subRegister", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result subRegister(@RequestBody MemberEntity member , HttpSession session){ public Result subRegister(@RequestBody MemberEntity member, HttpSession session) {
// 调用会员服务的register方法传入包含用户注册信息的MemberEntity对象进行用户注册操作返回包含操作结果信息的Result对象其中包含状态码、提示信息以及可能的业务数据等内容。
Result result = iMemberService.register(member); Result result = iMemberService.register(member);
if(result.getCode() == 200) {
// 判断返回的Result对象中的状态码是否为200表示用户注册操作成功通常意味着用户信息已成功保存到数据库等情况符合预期此时尝试进行登录操作。
if (result.getCode() == 200) {
// 获取Shiro的Subject对象它代表了当前执行的用户主体用于后续进行登录认证等操作。
Subject subject = ShiroUtils.getSubject(); Subject subject = ShiroUtils.getSubject();
// 从注册成功返回的Result对象中获取包含注册后的用户信息的MemberEntity对象用于构建登录凭证用户名和密码
MemberEntity memberEntity = (MemberEntity) result.getResult(); MemberEntity memberEntity = (MemberEntity) result.getResult();
// 创建一个UsernamePasswordToken对象将注册后的用户名和用户注册时填写的密码封装进去作为登录认证的凭证传递给Shiro框架。
UsernamePasswordToken token = new UsernamePasswordToken(memberEntity.getUsername(), member.getPassword()); UsernamePasswordToken token = new UsernamePasswordToken(memberEntity.getUsername(), member.getPassword());
try { try {
// 通过Subject对象发起登录认证操作Shiro框架会根据配置的认证逻辑如从数据库查询用户信息并比对密码等进行验证。
subject.login(token); subject.login(token);
// 如果登录成功将登录后的用户信息保存到HttpSession中方便在后续的请求处理中获取当前登录用户的相关信息键为"currMember"。
session.setAttribute("currMember", ShiroUtils.getMember()); session.setAttribute("currMember", ShiroUtils.getMember());
} catch (UnknownAccountException e) { } catch (UnknownAccountException e) {
// 如果Shiro认证过程中抛出UnknownAccountException异常表示用户名不存在或者未找到对应的用户信息返回相应的错误提示信息的Result对象给前端。
return Result.result(201, null, "用户名或密码有误,请重新输入或找回密码"); return Result.result(201, null, "用户名或密码有误,请重新输入或找回密码");
} catch (IncorrectCredentialsException e) { } catch (IncorrectCredentialsException e) {
// 如果Shiro认证过程中抛出IncorrectCredentialsException异常表示密码错误返回相应的错误提示信息的Result对象给前端。
return Result.result(202, null, "用户名或密码有误,请重新输入或找回密码"); return Result.result(202, null, "用户名或密码有误,请重新输入或找回密码");
} catch (LockedAccountException e) { } catch (LockedAccountException e) {
// 如果Shiro认证过程中抛出LockedAccountException异常表示账号被锁定返回相应的错误提示信息的Result对象给前端。
return Result.result(203, null, "账号被锁定"); return Result.result(203, null, "账号被锁定");
} }
} }
// 返回包含注册结果信息的Result对象若注册成功且登录成功则返回正常的Result对象包含注册后的用户等相关成功信息若注册失败或者登录出现异常则返回相应的错误提示信息的Result对象前端可根据此结果进行相应处理。
return result; return result;
} }

@ -26,63 +26,121 @@ import com.tamguo.util.Result;
/** /**
* Controller - * Controller -
* SpringSubjectWeb
* JSON
* *
* @author candy.tam * @author candy.tam
*
*/ */
@Controller @Controller
public class SubjectController { public class SubjectController {
// 创建日志记录器,用于记录在处理各类请求过程中出现的关键信息(如正常的业务日志、异常信息等),方便后续查看日志进行调试、排查问题以及了解系统运行情况。
private Logger logger = LoggerFactory.getLogger(getClass()); private Logger logger = LoggerFactory.getLogger(getClass());
// 自动注入IChapterService用于处理章节相关的业务逻辑例如根据书籍ID查找课程对应的章节列表等操作在展示学科相关页面时可能需要获取章节信息进行展示。
@Autowired @Autowired
private IChapterService iChapterService; private IChapterService iChapterService;
// 自动注入IAreaService用于处理地区相关的业务逻辑比如查找根地区列表等操作可能在学科相关页面展示地区筛选信息或者其他与地区相关的功能中会用到该服务获取的数据。
@Autowired @Autowired
private IAreaService iAreaService; private IAreaService iAreaService;
// 自动注入ISubjectService用于处理学科相关的业务逻辑像根据学科ID查找学科实体、获取课程树结构数据、获取级联选择器形式的课程树数据等操作都是通过该服务来完成是处理学科相关请求的核心服务依赖。
@Autowired @Autowired
private ISubjectService iSubjectService; private ISubjectService iSubjectService;
// 自动注入IBookService用于处理书籍相关的业务逻辑例如根据课程ID查询相关书籍列表等操作在获取学科相关信息时可能会涉及书籍信息的查询进而基于书籍信息获取其他相关数据如章节信息等
@Autowired @Autowired
private IBookService iBookService; private IBookService iBookService;
/**
* GETIDsubjectId
* ModelAndView"subject"ModelAndView
* "500"ModelAndView500
*
* @param subjectId
* @param model ModelAndView
* @return ModelAndViewModelAndView
*/
@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 , ModelAndView model) { public ModelAndView indexAction(@PathVariable String subjectId, ModelAndView model) {
try { try {
// 根据传入的学科ID通过学科服务查询并获取对应的学科实体信息用于在学科详细页面展示学科的基本信息、关联课程等相关内容。
SubjectEntity subject = iSubjectService.find(subjectId); SubjectEntity subject = iSubjectService.find(subjectId);
// 获取第一个科目
// 获取该学科下的第一个课程实体信息,这里假设取第一个课程作为某种默认展示或者后续处理的基础(具体业务逻辑可能需根据实际情况调整),
// 比如后续基于该课程查找相关书籍、章节等信息,方便在页面上展示与该学科相关的课程、书籍、章节等内容的关联关系。
CourseEntity course = subject.getCourseList().get(0); CourseEntity course = subject.getCourseList().get(0);
// 获取第一本书
// 通过条件查询,查找与当前课程相关的所有书籍列表,可能用于展示该课程下有哪些相关书籍等场景,以便在学科详细页面呈现更丰富的关联信息。
List<BookEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", course.getUid())); List<BookEntity> bookList = iBookService.selectList(Condition.create().eq("course_id", course.getUid()));
// 获取书籍列表中的第一本图书实体信息(这里同样可能是基于某种默认展示或后续处理的考虑,取第一本作为代表,具体根据业务需求确定),
// 后续可能基于该书籍去查找对应的章节信息等内容,用于在学科详细页面展示课程下书籍包含的章节情况。
BookEntity book = bookList.get(0); BookEntity book = bookList.get(0);
// 通过章节服务,查询并获取当前书籍对应的章节列表信息,用于在学科详细页面展示该课程下书籍所包含的章节内容等,方便用户查看学科相关的具体知识章节情况。
List<ChapterEntity> chapterList = iChapterService.findCourseChapter(book.getUid()); List<ChapterEntity> chapterList = iChapterService.findCourseChapter(book.getUid());
model.setViewName("subject");
model.addObject("subject", subject); // 设置视图名称为"subject",对应相应的学科详细页面模板,后续会根据这个名称去查找并渲染对应的视图展示给用户。
model.addObject("course" , course); model.setViewName("subject");
model.addObject("courseList", subject.getCourseList());
model.addObject("chapterList" , chapterList); // 将查询到的学科实体信息添加到ModelAndView对象中以便在视图中展示学科的详细信息如学科名称、学科描述等内容。
model.addObject("areaList", iAreaService.findRootArea()); model.addObject("subject", subject);
return model;
// 将获取到的第一个课程实体信息添加到ModelAndView对象中以便在视图中展示学科下的课程相关信息可能展示课程名称、课程简介等内容具体根据前端页面需求确定
model.addObject("course", course);
// 将学科包含的所有课程列表信息添加到ModelAndView对象中可能用于在页面上展示该学科下所有可选的课程方便用户切换查看不同课程对应的书籍、章节等信息具体根据前端展示需求确定
model.addObject("courseList", subject.getCourseList());
// 将查询到的章节列表信息添加到ModelAndView对象中以便在视图中展示课程下书籍所包含的章节内容方便用户查看具体的知识章节情况。
model.addObject("chapterList", chapterList);
// 通过地区服务查找根地区列表信息可能用于在学科详细页面提供地区筛选等功能例如按地区查找该学科相关资源等情况具体根据业务逻辑和前端展示需求确定并添加到ModelAndView对象中。
model.addObject("areaList", iAreaService.findRootArea());
// 返回包含视图名称和各种相关数据的ModelAndView对象以便进行视图渲染并展示给用户学科详细页面内容。
return model;
} catch (Exception e) { } catch (Exception e) {
logger.error(e.getMessage() , e); // 如果在查询相关数据过程中出现异常,将异常信息记录到日志中,方便后续查看问题原因、进行调试等操作,记录的信息包括异常消息以及详细的异常堆栈信息。
logger.error(e.getMessage(), e);
// 将视图名称设置为"500",通常用于表示服务器内部出现错误的情况,意味着出现了未预期的异常导致无法正常处理请求并返回正确的视图内容。
model.setViewName("500"); model.setViewName("500");
// 返回包含错误视图名称的ModelAndView对象可能会渲染出500页面展示给用户告知出现了服务器内部错误。
return model; return model;
} }
} }
/**
* GETgetCourseTreeJSONArray
* Result便使
*
* @return ResultResultJSONArray
*/
@RequestMapping(value = {"subject/getCourseTree.html"}, method = RequestMethod.GET) @RequestMapping(value = {"subject/getCourseTree.html"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result getCourseTree(){ public Result getCourseTree() {
// 调用学科服务的getCourseTree方法获取课程树结构数据返回的数据格式为JSONArray其中包含了课程之间的层级关系等相关信息具体结构和内容由服务层实现决定
JSONArray list = iSubjectService.getCourseTree(); JSONArray list = iSubjectService.getCourseTree();
// 将获取到的课程树结构数据包装在表示成功的Result对象中返回给前端方便前端按照统一的格式进行处理Result对象中的数据字段存放实际的课程树信息这里就是JSONArray类型的list
return Result.successResult(list); return Result.successResult(list);
} }
// [{"value":"11","label":"北京市","children":[{"value":"1101","label":"市辖区"}]}] /**
* GETgetCourseCascaderTreeJSONArrayvaluelabelchildren
* Result便使便
*
* @return ResultResultJSONArray
*/
@RequestMapping(value = {"subject/getCourseCascaderTree"}, method = RequestMethod.GET) @RequestMapping(value = {"subject/getCourseCascaderTree"}, method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result getCourseCascaderTree() { public Result getCourseCascaderTree() {
// 调用学科服务的getCourseCascaderTree方法获取级联选择器形式的课程树数据返回的数据格式为JSONArray其结构符合级联选择器所需的格式例如示例中展示的包含节点的value、label以及子节点children等信息
JSONArray list = iSubjectService.getCourseCascaderTree(); JSONArray list = iSubjectService.getCourseCascaderTree();
// 将获取到的级联选择器形式的课程树数据包装在表示成功的Result对象中返回给前端方便前端按照统一的格式进行处理Result对象中的数据字段存放实际的课程树信息这里就是JSONArray类型的list
return Result.successResult(list); return Result.successResult(list);
} }
} }

@ -26,86 +26,167 @@ import com.tamguo.dao.redis.CacheService;
import com.tamguo.util.DateUtils; import com.tamguo.util.DateUtils;
import com.tamguo.util.Setting; import com.tamguo.util.Setting;
// 标识这是一个Spring的控制器类主要用于处理与UEditor相关的Web请求例如返回UEditor的配置信息以及处理图片上传等功能
// 在图片上传过程中涉及文件的存储操作、根据日期生成文件路径、利用缓存生成唯一文件名编号等逻辑,同时对操作过程中的异常进行了相应处理并返回合适的结果给前端。
@Controller @Controller
public class UEditorController { public class UEditorController {
// 创建日志记录器用于记录在处理UEditor相关请求过程中出现的关键信息如正常的业务日志、文件上传成功或失败信息、异常信息等方便后续查看日志进行调试、排查问题以及了解系统运行情况。
private Logger logger = org.slf4j.LoggerFactory.getLogger(getClass()); private Logger logger = org.slf4j.LoggerFactory.getLogger(getClass());
// 通过@Value注解注入配置文件中配置的文件存储路径属性值该路径用于指定上传文件在服务器端存储的基础目录位置后续会根据日期等信息进一步构建具体的文件存储子目录。
@Value("${file.storage.path}") @Value("${file.storage.path}")
private String fileStoragePath; private String fileStoragePath;
// 自动注入Setting对象该对象可能包含了系统的一些全局设置信息例如域名等相关配置在处理文件上传后的文件访问路径等相关逻辑时可能会用到其中的配置数据。
@Autowired @Autowired
private Setting setting; private Setting setting;
// 自动注入CacheService用于与缓存系统进行交互在这里主要是利用缓存来生成UEditor上传文件的唯一编号以保证文件名的唯一性便于文件管理和区分不同时间上传的文件。
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
// 定义一个表示UEditor文件编号无格式的常量字符串用于后续格式化生成唯一编号时作为格式模板这里"00000"表示生成的编号会按照五位数字进行格式化填充前面补0具体用途在生成文件名编号逻辑中体现。
private static final String UEDITOR_NO_FORMAT = "00000"; private static final String UEDITOR_NO_FORMAT = "00000";
private static final String UEDITOR_PREFIX = "UEDITOR";
// 定义一个UEditor相关的文件名前缀常量字符串用于构建上传文件的唯一文件名方便在文件系统中对UEditor上传的文件进行统一标识和区分与后面生成的编号等组合形成完整的文件名。
private static final String UEDITOR_PREFIX = "UEDITOR";
@RequestMapping(value="/ueditor") /**
@ResponseBody * UEditorUEditorJSONUEditorConfig.UEDITOR_CONFIGJSONObject
public JSONObject ueditor(HttpServletRequest request) { * UEditor使
return JSONObject.parseObject(UEditorConfig.UEDITOR_CONFIG); *
} * @param request HttpServletRequest使
* @return JSONObjectUEditor
*/
@RequestMapping(value = "/ueditor")
@ResponseBody
public JSONObject ueditor(HttpServletRequest request) {
return JSONObject.parseObject(UEditorConfig.UEDITOR_CONFIG);
}
@RequestMapping(value="/imgUpload") /**
@ResponseBody * MultipartFileupfile
public Ueditor imgUpload(MultipartFile upfile) throws IOException { * 访URLUeditorUeditor
if (!upfile.isEmpty()) { *
* @param upfile MultipartFile
* @return Ueditor"SUCCESS"访URL"ERROR""上传失败""File is empty"
* @throws IOException I/O
*/
@RequestMapping(value = "/imgUpload")
@ResponseBody
public Ueditor imgUpload(MultipartFile upfile) throws IOException {
// 判断上传的文件是否为空如果不为空则进行后续的文件保存等操作为空则直接返回表示错误状态文件为空的Ueditor对象给前端。
if (!upfile.isEmpty()) {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
// 根据当前日期年、月、日构建文件存储的子目录路径将其与配置的文件存储基础路径fileStoragePath拼接起来形成完整的文件存储目录路径
// 这样可以按照日期对上传的文件进行分类存储,便于管理和查找不同时间上传的文件。
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);
// 判断文件存储目录是否存在,如果不存在则创建该目录,确保后续可以将文件保存到正确的目录位置。
if (!dir.exists()) if (!dir.exists())
dir.mkdirs(); dir.mkdirs();
// 调用getUEditorNo方法生成一个唯一的文件名编号该编号结合文件原始名称的后缀通过截取原始文件名中最后一个"."之后的部分获取后缀),构建出完整的服务器端存储文件名,
// 以此保证文件名在整个系统中的唯一性,便于文件管理和区分不同的上传文件。
String fileName = this.getUEditorNo() + upfile.getOriginalFilename().substring(upfile.getOriginalFilename().lastIndexOf(".")); String fileName = this.getUEditorNo() + upfile.getOriginalFilename().substring(upfile.getOriginalFilename().lastIndexOf("."));
File serverFile = new File(dir + File.separator + fileName); File serverFile = new File(dir + File.separator + fileName);
// 获取上传文件的输入流,用于读取文件内容,后续将通过该输入流把文件内容写入到服务器端的目标文件中。
in = upfile.getInputStream(); in = upfile.getInputStream();
// 创建一个指向服务器端目标文件的输出流,用于将从上传文件读取到的内容写入到该文件中,实现文件保存到服务器的操作。
out = new FileOutputStream(serverFile); out = new FileOutputStream(serverFile);
// 创建一个字节数组缓冲区用于批量读取和写入文件内容每次读取1024字节的数据提高文件读写效率避免逐个字节读写的低效率情况
byte[] b = new byte[1024]; byte[] b = new byte[1024];
int len = 0; int len = 0;
// 通过循环不断从输入流中读取数据到字节数组缓冲区每次最多读取1024字节直到读取完整个文件内容即读取的字节数小于等于0表示已读完
// 然后将缓冲区中的数据写入到输出流对应的服务器端文件中,实现文件内容的复制保存操作。
while ((len = in.read(b)) > 0) { while ((len = in.read(b)) > 0) {
out.write(b, 0, len); out.write(b, 0, len);
} }
// 关闭输出流,释放相关资源,确保文件写入操作完成后正确关闭资源,避免资源泄漏等问题。
out.close(); out.close();
// 关闭输入流,释放相关资源,确保文件读取操作完成后正确关闭资源,避免资源泄漏等问题。
in.close(); in.close();
// 将服务器端保存文件的绝对路径信息记录到日志中,方便后续查看文件实际存储位置以及排查文件相关的问题(如文件是否保存成功等情况)。
logger.info("Server File Location=" + serverFile.getAbsolutePath()); logger.info("Server File Location=" + serverFile.getAbsolutePath());
Ueditor ueditor = new Ueditor(); // 创建一个Ueditor对象用于返回文件上传的结果信息给前端。
ueditor.setState("SUCCESS"); Ueditor ueditor = new Ueditor();
ueditor.setUrl(setting.domain + "files" + "/" +DateUtils.format(new Date(), "yyyyMMdd") + "/" + fileName); // 设置文件上传状态为"SUCCESS",表示文件上传成功,前端可以根据该状态进行相应的提示信息展示等处理(例如显示上传成功的提示框)。
return ueditor; ueditor.setState("SUCCESS");
// 构建文件的访问URL将系统配置的域名通过Setting对象获取、固定的文件访问路径前缀"files"、按照日期生成的文件存储子目录通过DateUtils.format生成以及最终的文件名拼接起来
// 形成完整的文件访问URL前端可以使用该URL来访问上传后的文件例如在编辑器中展示上传的图片等场景
ueditor.setUrl(setting.domain + "files" + "/" + DateUtils.format(new Date(), "yyyyMMdd") + "/" + fileName);
// 返回包含成功状态及文件访问URL等信息的Ueditor对象给前端告知文件上传成功以及可以通过返回的URL访问该文件。
return ueditor;
} catch (Exception e) { } catch (Exception e) {
// 如果在文件上传过程中出现异常如文件保存失败、流操作异常等情况创建一个表示错误状态的Ueditor对象用于返回错误信息给前端。
Ueditor ueditor = new Ueditor(); Ueditor ueditor = new Ueditor();
ueditor.setState("ERROR"); // 设置文件上传状态为"ERROR",表示文件上传失败,前端可以根据该状态进行相应的提示信息展示(例如显示上传失败的提示框)。
ueditor.setTitle("上传失败"); ueditor.setState("ERROR");
return ueditor; // 设置错误提示标题为"上传失败",用于更详细地告知前端用户文件上传出现问题的大致情况,前端可根据此标题进行更具体的错误提示展示(具体展示逻辑由前端决定)。
ueditor.setTitle("上传失败");
// 返回包含错误状态及错误提示标题的Ueditor对象给前端告知文件上传失败及失败原因通过标题提示
return ueditor;
} finally { } finally {
if (out != null) { // 在finally块中确保输出流资源能够正确关闭避免因异常等情况导致资源未释放的问题先判断输出流是否不为空如果不为空则关闭输出流并将其置为null表示资源已释放。
if (out!= null) {
out.close(); out.close();
out = null; out = null;
} }
if (in != null) { // 在finally块中确保输入流资源能够正确关闭避免因异常等情况导致资源未释放的问题先判断输入流是否不为空如果不为空则关闭输入流并将其置为null表示资源已释放。
if (in!= null) {
in.close(); in.close();
in = null; in = null;
} }
} }
} else { } else {
// 如果上传的文件为空创建一个表示错误状态的Ueditor对象用于返回错误信息给前端。
Ueditor ueditor = new Ueditor(); Ueditor ueditor = new Ueditor();
ueditor.setState("ERROR"); // 设置文件上传状态为"ERROR",表示文件上传失败,前端可以根据该状态进行相应的提示信息展示(例如显示上传失败的提示框)。
ueditor.setTitle("File is empty"); ueditor.setState("ERROR");
return ueditor; // 设置错误提示标题为"File is empty",明确告知前端用户上传的文件为空,前端可根据此标题进行相应的错误提示展示(具体展示逻辑由前端决定)。
ueditor.setTitle("File is empty");
// 返回包含错误状态及错误提示标题的Ueditor对象给前端告知文件上传失败及失败原因文件为空
return ueditor;
} }
} }
/**
* UEditor"yyyyMM"
* UEDITOR_PREFIXCacheService
* UEDITOR_NO_FORMAT0
*
* @return UEditor便
*/
private String getUEditorNo() { private String getUEditorNo() {
// 创建一个SimpleDateFormat对象用于按照"yyyyMM"格式对日期进行格式化,即获取当前日期的年和月信息,用于构建与日期相关的唯一编号部分。
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
String format = sdf.format(new Date()); String format = sdf.format(new Date());
// 创建一个DecimalFormat对象使用定义的UEDITOR_NO_FORMAT常量作为格式化模板用于将后续生成的自增编号按照五位数字的格式进行格式化不足五位前面补0保证编号格式的一致性和唯一性。
DecimalFormat df = new DecimalFormat(UEDITOR_NO_FORMAT); DecimalFormat df = new DecimalFormat(UEDITOR_NO_FORMAT);
// 构建缓存中的键将固定的UEDITOR_PREFIX前缀与当前日期格式字符串format拼接起来形成一个唯一标识当前月份的UEditor文件编号相关的键用于在缓存中进行计数操作。
String key = UEDITOR_PREFIX + format; String key = UEDITOR_PREFIX + format;
// 通过缓存服务CacheService对构建的键对应的值进行自增操作每次调用该方法时对应键的值会在原来基础上加1以此生成顺序递增且唯一的编号值返回自增后的编号值。
Long incr = cacheService.incr(key); Long incr = cacheService.incr(key);
// 将自增后的编号值按照定义的格式通过DecimalFormat进行格式化进行处理与固定的UEDITOR_PREFIX前缀再次拼接形成完整的唯一文件名编号用于后续构建上传文件的文件名保证文件名的唯一性。
String avatorNo = UEDITOR_PREFIX + df.format(incr); String avatorNo = UEDITOR_PREFIX + df.format(incr);
return avatorNo; return avatorNo;
} }

@ -35,76 +35,133 @@ import com.tamguo.util.ShiroUtils;
import com.tamguo.util.Status; import com.tamguo.util.Status;
import com.tamguo.util.UploaderMessage; import com.tamguo.util.UploaderMessage;
// 标识这是一个Spring的控制器类用于处理Web请求并返回相应的视图或数据
@Controller @Controller
public class AccountController { public class AccountController {
// 自动注入IMemberService用于处理会员相关的业务逻辑
@Autowired @Autowired
public IMemberService memberService; public IMemberService memberService;
// 通过配置文件注入文件存储路径属性值,方便后续文件保存操作使用
@Value("${file.storage.path}") @Value("${file.storage.path}")
private String fileStoragePath; private String fileStoragePath;
// 自动注入Setting可能用于获取一些系统配置相关的设置信息
@Autowired @Autowired
private Setting setting; private Setting setting;
// 自动注入CacheService用于缓存相关操作例如在生成头像编号时使用缓存来保证编号的唯一性和递增性
@Autowired @Autowired
private CacheService cacheService; private CacheService cacheService;
// 定义头像编号的默认格式化字符串,用于格式化生成的编号,保证编号格式统一
private static final String AVATOR_NO_FORMAT = "00000"; private static final String AVATOR_NO_FORMAT = "00000";
// 定义头像编号的前缀,方便识别头像文件相关的编号
private static final String AVATOR_PREFIX = "MTX"; private static final String AVATOR_PREFIX = "MTX";
// 创建日志记录器,用于记录该类中关键操作的日志信息,方便调试和问题排查
public Logger logger = LoggerFactory.getLogger(getClass()); public Logger logger = LoggerFactory.getLogger(getClass());
/**
* GET
*
* @param model ModelAndView
* @param session HttpSession
* @return ModelAndView便
*/
@RequestMapping(value = "member/account", method = RequestMethod.GET) @RequestMapping(value = "member/account", 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.getUid())); model.addObject("member", memberService.findByUid(member.getUid()));
return model; return model;
} }
/**
* POSTIDID
*
* @param member MemberEntity
* @return Result
*/
@RequestMapping(value = "member/account/update", method = RequestMethod.POST) @RequestMapping(value = "member/account/update", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result updateMember(@RequestBody MemberEntity member){ public Result updateMember(@RequestBody MemberEntity member) {
member.setUid(ShiroUtils.getUserId()); member.setUid(ShiroUtils.getUserId());
memberService.updateMember(member); memberService.updateMember(member);
return Result.successResult(member); return Result.successResult(member);
} }
/**
* GET
*
* @param model ModelAndView
* @return ModelAndView便
*/
@RequestMapping(value = "member/password", method = RequestMethod.GET) @RequestMapping(value = "member/password", 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;
} }
/**
* POST
*
* @param member MemberEntity
* @return Result
*/
@RequestMapping(value = "member/password/update", method = RequestMethod.POST) @RequestMapping(value = "member/password/update", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result updatePwd(@RequestBody MemberEntity member){ public Result updatePwd(@RequestBody MemberEntity member) {
return memberService.updatePwd(member); return memberService.updatePwd(member);
} }
/**
* POSTHttpServletRequest
*
* @param file MultipartFile
* @param request HttpServletRequest使
* @return UploaderMessage
* @throws IOException I/O
*/
@RequestMapping(value = "/member/uploadFile", method = RequestMethod.POST) @RequestMapping(value = "/member/uploadFile", 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 {
// 判断上传的文件是否为空,如果为空则直接返回文件为空的错误提示信息
if (!file.isEmpty()) { if (!file.isEmpty()) {
InputStream in = null; InputStream in = null;
OutputStream out = null; OutputStream out = null;
try { try {
// 根据当前日期构建文件存储的路径,方便按日期分类存储文件
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);
// 如果目录不存在,则创建相应的目录,确保文件有存储的位置
if (!dir.exists()) if (!dir.exists())
dir.mkdirs(); dir.mkdirs();
// 生成头像文件名,结合缓存获取的编号以及原文件名的后缀组成完整的文件名
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();
// 创建文件输出流,用于将读取的文件内容写入到服务器指定的存储位置
out = new FileOutputStream(path + "/" + avatorName); out = new FileOutputStream(path + "/" + avatorName);
byte[] b = new byte[1024]; byte[] b = new byte[1024];
int len = 0; int len = 0;
// 通过循环读取输入流中的文件内容,并写入到输出流中,实现文件的复制保存操作
while ((len = in.read(b)) > 0) { while ((len = in.read(b)) > 0) {
out.write(b, 0, len); out.write(b, 0, len);
} }
// 关闭输出流,释放资源
out.close(); out.close();
// 关闭输入流,释放资源
in.close(); in.close();
// 记录文件在服务器上的存储位置信息到日志中,方便后续查看和排查问题
logger.info("Server File Location=" + path + avatorName); logger.info("Server File Location=" + path + avatorName);
// 创建表示文件上传成功的消息对象,并设置相应的成功状态、提示信息、文件路径以及文件所在的域名等信息
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");
@ -112,22 +169,26 @@ public class AccountController {
msg.setFileDomain(setting.domain); msg.setFileDomain(setting.domain);
return msg; return msg;
} catch (Exception e) { } catch (Exception e) {
// 如果在文件上传过程中出现异常,创建表示文件上传失败的消息对象,并设置相应的错误状态和提示信息,然后返回该对象告知前端上传失败
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 { } finally {
if (out != null) { // 在最终块中确保输出流资源被正确关闭,避免资源泄露
if (out!= null) {
out.close(); out.close();
out = null; out = null;
} }
if (in != null) { // 在最终块中确保输入流资源被正确关闭,避免资源泄露
if (in!= null) {
in.close(); in.close();
in = null; in = null;
} }
} }
} else { } else {
// 如果上传的文件为空,创建表示文件为空的错误消息对象,并设置相应的错误状态和提示信息,然后返回该对象告知前端文件为空
UploaderMessage msg = new UploaderMessage(); UploaderMessage msg = new UploaderMessage();
msg.setStatus(Status.ERROR); msg.setStatus(Status.ERROR);
msg.setError("File is empty"); msg.setError("File is empty");
@ -135,7 +196,11 @@ public class AccountController {
} }
} }
/**
*
*
* @return
*/
private String getAvatorNo() { private String getAvatorNo() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM"); SimpleDateFormat sdf = new SimpleDateFormat("yyyyMM");
String format = sdf.format(new Date()); String format = sdf.format(new Date());

@ -19,72 +19,123 @@ import com.tamguo.service.IPaperService;
import com.tamguo.util.ExceptionSupport; import com.tamguo.util.ExceptionSupport;
import com.tamguo.util.Result; import com.tamguo.util.Result;
// 标识这是一个Spring的控制器类用于处理Web请求并返回相应的视图或数据
@Controller @Controller
public class MemberPaperController { public class MemberPaperController {
// 自动注入IPaperService用于处理试卷相关的业务逻辑比如试卷的查询、添加、修改、删除等操作
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
/**
* GET"member/paperList"
*
* @param model ModelAndView
* @param session HttpSession使
* @return ModelAndView便
*/
@RequestMapping(value = "/member/paper", method = RequestMethod.GET) @RequestMapping(value = "/member/paper", method = RequestMethod.GET)
public ModelAndView paper(ModelAndView model, HttpSession session){ public ModelAndView paper(ModelAndView model, HttpSession session) {
model.setViewName("member/paperList"); model.setViewName("member/paperList");
return model; return model;
} }
/**
* GETIDResult
*
* @param paperId
* @return ResultResult
*/
@RequestMapping(value = "/member/findPaper", method = RequestMethod.GET) @RequestMapping(value = "/member/findPaper", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result findPaper(String paperId) { public Result findPaper(String paperId) {
return Result.successResult(iPaperService.find(paperId)); return Result.successResult(iPaperService.find(paperId));
} }
@RequestMapping(value = "member/paper/list" , method = RequestMethod.GET) /**
* GET
* Map便jqGrid
*
* @param name
* @param page
* @param limit
* @param session HttpSession
* @return Map
*/
@RequestMapping(value = "member/paper/list", 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")); MemberEntity member = ((MemberEntity) session.getAttribute("currMember"));
Page<PaperEntity> p = new Page<>(); Page<PaperEntity> p = new Page<>();
p.setCurrent(page); p.setCurrent(page);
p.setSize(limit); p.setSize(limit);
Page<PaperEntity> list = iPaperService.memberPaperList(name, member.getUid() , p); Page<PaperEntity> list = iPaperService.memberPaperList(name, member.getUid(), p);
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",method=RequestMethod.POST) /**
* POSTJSONID
* Result
*
* @param data JSONObject
* @return Result
*/
@RequestMapping(value = "member/paperList/addPaperQuestionInfo", 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; String paperId;
String title;
String name;
String type;
paperId = data.getString("uid"); paperId = data.getString("uid");
title = data.getString("title"); title = data.getString("title");
type = data.getString("type"); type = data.getString("type");
name = QuestionType.getQuestionType(type).getDesc(); name = QuestionType.getQuestionType(type).getDesc();
iPaperService.addPaperQuestionInfo(paperId , title , name , type); iPaperService.addPaperQuestionInfo(paperId, title, name, type);
return Result.result(0, null, "修改成功"); return Result.result(0, null, "修改成功");
} catch (Exception e) { } catch (Exception e) {
return ExceptionSupport.resolverResult("添加questionInfo", this.getClass(), e); return ExceptionSupport.resolverResult("添加questionInfo", this.getClass(), e);
} }
} }
/**
* POSTJSONID
* Result
*
* @param data JSONObject
* @return Result
*/
@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; 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");
name = QuestionType.getQuestionType(type).getDesc(); name = QuestionType.getQuestionType(type).getDesc();
uid = data.getString("infoUid"); uid = data.getString("infoUid");
iPaperService.updatePaperQuestionInfo(paperId , title , name , type , uid); iPaperService.updatePaperQuestionInfo(paperId, title, name, type, uid);
return Result.result(0, null, "修改成功"); return Result.result(0, null, "修改成功");
} catch (Exception e) { } catch (Exception e) {
return ExceptionSupport.resolverResult("修改questionInfo", this.getClass(), e); return ExceptionSupport.resolverResult("修改questionInfo", this.getClass(), e);
} }
} }
/**
* IDResult
*
* @param paperId
* @return Result
*/
@RequestMapping("member/paperList/deletePaper") @RequestMapping("member/paperList/deletePaper")
@ResponseBody @ResponseBody
public Result deletePaper(String paperId){ public Result deletePaper(String paperId) {
try { try {
return iPaperService.deletePaper(paperId); return iPaperService.deletePaper(paperId);
} catch (Exception e) { } catch (Exception e) {
@ -92,32 +143,54 @@ public class MemberPaperController {
} }
} }
/**
* ID
* Result
*
* @param paperId
* @param uid
* @return Result
*/
@RequestMapping("member/paperList/deletePaperQuestionInfoBtn") @RequestMapping("member/paperList/deletePaperQuestionInfoBtn")
@ResponseBody @ResponseBody
public Result deletePaperQuestionInfoBtn(String paperId , String uid){ public Result deletePaperQuestionInfoBtn(String paperId, String uid) {
try { try {
return iPaperService.deletePaperQuestionInfoBtn(paperId , uid); return iPaperService.deletePaperQuestionInfoBtn(paperId, uid);
} catch (Exception e) { } catch (Exception e) {
return ExceptionSupport.resolverResult("删除子卷", this.getClass(), e); return ExceptionSupport.resolverResult("删除子卷", this.getClass(), e);
} }
} }
@RequestMapping(value="member/paperList/addPaper.html",method=RequestMethod.POST) /**
* POSTIDResult
*
*
* @param paper PaperEntity
* @param session HttpSessionID
* @return Result
*/
@RequestMapping(value = "member/paperList/addPaper.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result addPaper(@RequestBody PaperEntity paper,HttpSession session){ public Result addPaper(@RequestBody PaperEntity paper, HttpSession session) {
try { try {
MemberEntity member = (MemberEntity) session.getAttribute("currMember"); MemberEntity member = (MemberEntity) session.getAttribute("currMember");
paper.setCreaterId(member.getUid()); paper.setCreaterId(member.getUid());
iPaperService.addPaper(paper); iPaperService.addPaper(paper);
return Result.result(Result.SUCCESS_CODE, paper, "添加成功"); return Result.result(Result.SUCCESS_CODE, paper, "添加成功");
} catch (Exception e) { } catch (Exception e) {
return ExceptionSupport.resolverResult("添加试卷", this.getClass(), e); return ExceptionSupport.resolverResult("添加试卷", this.getClass(), e);
} }
} }
@RequestMapping(value="member/paperList/updatePaper.html",method=RequestMethod.POST) /**
* POSTResult
*
* @param paper PaperEntity
* @return Result
*/
@RequestMapping(value = "member/paperList/updatePaper.html", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result updatePaper(@RequestBody PaperEntity paper){ public Result updatePaper(@RequestBody PaperEntity paper) {
try { try {
return iPaperService.updatePaper(paper); return iPaperService.updatePaper(paper);
} catch (Exception e) { } catch (Exception e) {

@ -17,24 +17,43 @@ import com.tamguo.service.IQuestionService;
import com.tamguo.util.ExceptionSupport; import com.tamguo.util.ExceptionSupport;
import com.tamguo.util.Result; import com.tamguo.util.Result;
@Controller(value="memberQuestionController") // 标识这是一个Spring的控制器类名为"memberQuestionController"用于处理与会员相关的试题操作的Web请求并返回相应的视图或数据
@Controller(value = "memberQuestionController")
public class QuestionController { public class QuestionController {
// 自动注入IQuestionService用于处理试题相关的业务逻辑例如试题的添加、查询、更新、删除等操作
@Autowired @Autowired
private IQuestionService iQuestionService; private IQuestionService iQuestionService;
// 自动注入IPaperService用于获取试卷相关信息可能在试题与试卷关联等操作中会用到
@Autowired @Autowired
private IPaperService iPaperService; private IPaperService iPaperService;
/**
* GETID"member/addQuestion"
* ModelAndView便
*
* @param paperId
* @param model ModelAndView
* @return ModelAndView
*/
@RequestMapping(value = "/member/addQuestion", method = RequestMethod.GET) @RequestMapping(value = "/member/addQuestion", 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.find(paperId)); model.addObject("paper", iPaperService.find(paperId));
return model; return model;
} }
/**
* POSTResult
*
*
* @param question QuestionEntity
* @return Result
*/
@RequestMapping(value = "/member/submitQuestion", method = RequestMethod.POST) @RequestMapping(value = "/member/submitQuestion", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result submitQuestion(QuestionEntity question){ public Result submitQuestion(QuestionEntity question) {
try { try {
return iQuestionService.addQuestion(question); return iQuestionService.addQuestion(question);
} catch (Exception e) { } catch (Exception e) {
@ -42,52 +61,99 @@ public class QuestionController {
} }
} }
/**
* GETID"member/questionList"
* ModelAndView便
*
* @param paperId
* @param model ModelAndView
* @return ModelAndView
*/
@RequestMapping(value = "/member/questionList", method = RequestMethod.GET) @RequestMapping(value = "/member/questionList", method = RequestMethod.GET)
public ModelAndView questionList(String paperId , ModelAndView model){ public ModelAndView questionList(String paperId, ModelAndView model) {
model.addObject("paper", iPaperService.find(paperId)); model.addObject("paper", iPaperService.find(paperId));
model.setViewName("member/questionList"); model.setViewName("member/questionList");
return model; return model;
} }
@RequestMapping(value = "/member/queryQuestionList" , method=RequestMethod.POST) /**
* POSTJSONID
* Map便jqGrid
*
* @param data JSONObject
* @return Map
*/
@RequestMapping(value = "/member/queryQuestionList", 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 ; String questionType;
Integer page ; Integer limit; 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("questionContent");
paperId = data.getString("paperId"); paperId = data.getString("paperId");
page = data.getInteger("page"); page = data.getInteger("page");
limit = data.getInteger("limit"); limit = data.getInteger("limit");
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 , p); Page<QuestionEntity> list = iQuestionService.queryQuestionList(questionType, uid, content, paperId, p);
return Result.jqGridResult(list.getRecords(), list.getTotal(), limit, page, list.getPages()); return Result.jqGridResult(list.getRecords(), list.getTotal(), limit, page, list.getPages());
} }
/**
* GETIDID"member/editQuestion"
* IDIDModelAndView便
*
* @param paperId
* @param questionId
* @param model ModelAndViewID
* @return IDModelAndView
*/
@RequestMapping(value = "/member/editQuestion", method = RequestMethod.GET) @RequestMapping(value = "/member/editQuestion", 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.find(paperId)); model.addObject("paper", iPaperService.find(paperId));
model.addObject("questionId" , questionId); model.addObject("questionId", questionId);
return model; return model;
} }
/**
* GETIDResult
*
* @param questionId
* @return ResultResult
*/
@RequestMapping(value = "/member/getQuestion", method = RequestMethod.GET) @RequestMapping(value = "/member/getQuestion", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result getQuestion(String questionId) { public Result getQuestion(String questionId) {
return Result.successResult(iQuestionService.select(questionId)); return Result.successResult(iQuestionService.select(questionId));
} }
/**
* POSTResult
*
*
* @param question QuestionEntity
* @return Result
*/
@RequestMapping(value = "/member/updateQuestion", method = RequestMethod.POST) @RequestMapping(value = "/member/updateQuestion", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result updateQuestion(QuestionEntity question) { public Result updateQuestion(QuestionEntity question) {
return iQuestionService.updateQuestion(question); return iQuestionService.updateQuestion(question);
} }
/**
* GETResult
*
*
* @param uid
* @return Result
*/
@RequestMapping(value = "/member/deleteQuestion", method = RequestMethod.GET) @RequestMapping(value = "/member/deleteQuestion", method = RequestMethod.GET)
@ResponseBody @ResponseBody
public Result deleteQuestion(@RequestBody String uid) { public Result deleteQuestion(@RequestBody String uid) {

@ -15,18 +15,34 @@ import com.tamguo.model.TeacherEntity;
import com.tamguo.service.ITeacherService; import com.tamguo.service.ITeacherService;
import com.tamguo.util.Result; import com.tamguo.util.Result;
// 标识这是一个Spring的控制器类用于处理与教师相关的Web请求并返回相应的视图或数据
@Controller @Controller
public class JoinusController { public class JoinusController {
// 自动注入ITeacherService用于处理教师相关的业务逻辑比如根据手机号获取教师信息、教师注册加入等操作
@Autowired @Autowired
private ITeacherService iTeacherService; private ITeacherService iTeacherService;
/**
* GET"teacher/joinus"
*
* @param model ModelAndView
* @param session HttpSession使
* @return ModelAndView便
*/
@RequestMapping(value = "teacher/joinus", method = RequestMethod.GET) @RequestMapping(value = "teacher/joinus", 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;
} }
/**
* POSTMapmobileverifyCode
* Result
*
* @param param Map<String, Object>
* @return Result
*/
@RequestMapping(value = "teacher/info", method = RequestMethod.POST) @RequestMapping(value = "teacher/info", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result getTeacher(@RequestBody Map<String, Object> param) { public Result getTeacher(@RequestBody Map<String, Object> param) {
@ -36,6 +52,13 @@ public class JoinusController {
return result; return result;
} }
/**
* POSTTeacherEntityjoinus
* ResultResult
*
* @param teacher TeacherEntity
* @return Result
*/
@RequestMapping(value = "teacher/joinus", method = RequestMethod.POST) @RequestMapping(value = "teacher/joinus", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public Result teacherJoinus(@RequestBody TeacherEntity teacher) { public Result teacherJoinus(@RequestBody TeacherEntity teacher) {

Loading…
Cancel
Save