|  | 3 years ago | |
|---|---|---|
| README.md | 3 years ago | |
		
			
				
				README.md
			
		
		
			
			
		
	
	教研
代码结构
├─ Directory.Build.props(通用的配置默认给所有项目加载)
├─ NuGet.Config(Nuget配置)
├─ Office2Html(文件转换)
├─ Office2Pdf(文件转换)
├─ common.props(通用配置)
├─ host
│    └─ Sh.Platform.HttpApi.Host(启动项)
├─ plugins
│    ├─ Menu(菜单相关)
│    ├─ WeChat(微信相关)
│    ├─ contest(教研活动、作品提交及评审)
│    ├─ fangan(方案设计、主题作业、通知、家校相关)
│    ├─ file(文件上传、使用统计及配置)
│    ├─ jiaoyan(题库、组卷、资源库、直播、二次备课、教研活动查看等教研相关)
│    └─ school(学校、学生、年级班级、目录和知识点等基础数据)
├─ src(主模块)
│    ├─ 依赖了第三方库小程序模块,并重写了绑定流程
│    ├─ 依赖了各种基础模块,比如:用户、角色、权限、租户
│    ├─ 重写了用户密码加密方式
│    ├─ 添加了用户绑定邮箱、发送验证码、重置密码登录功能
│    ├─ 添加了监护人绑定功能(小程序)
各种地址
- 学校:ceshi1q2w3E*
- 教师(刘德华):00141q2w3E*
- 学生(宁红伟):NHW202200041q2w3E*
- admin:admin1q2w3E*
待做工作
- 单元测试
- 目前已经搭建了文件、教研、方案模块的测试
- 文件整体模块大体完成了 70%,只是一对一测试
- 教研完成了资源库的测试用例编写,其他还需完善
- 方案只搭建了测试项目
- 可以通过EasyAbp Helper根据模块名称创建模块之后,将测试项目直接复制过去,这样就避免了手动创建的麻烦
 
注意事项
1. 分支
jiaoyan-dev-zongheshijian-file 分支目前包含有文件统计服务及单元测试。如果要合并的话,需要将jiaoyan-dev-zongheshijian分支先合并到jiaoyan-dev-zongheshijian-file分支,之后再合并过去,然后删除文件分支。
2. 基础用户
由于历史原因,contest和school是两套系统结合起来的,而两套系统中都有用户,所幸的是因为 Abp 用户模块的独立性,只需要将扩展用户表对应起来就可以。school教师对应contest中的学生,学校两边相对应,请注意同步数据。
3. 微信小程序用户
这一块交互较为复杂混乱。
4. 文件实体配置
大体上已经配置完成了,现在的配置的方式类似于单向链表。
例如下面的配置,根节点是教研活动,而活动项不需要统计文件,但还是需要显示是哪个活动项的,就可以使用 AddEntityConfig 进行配置。
如果需要统计该实体包含哪些文件就需要使用 AddHasUploadContentEntity 进行配置。
Configure<FileServiceOptions>(options =>
{
    options.AddEntityConfig<Contest>(config =>
    {
        config.Root("教研活动", contest => contest.ContestName);
    });
    options.AddEntityConfig<ContestItem>(config =>
    {
        config.Parent<Contest>(contestItem => contestItem.ContestId);
        config.SetAliasAndTitle("活动项", contestItem => contestItem.ContestItemName);
    });
    options.AddHasUploadContentEntity<ContestItemWorkContent>(config =>
    {
        config.Parent<ContestItem>(content => content.ContestItemId);
    });
    options.AddHasUploadContentEntity<ContestItemWorkQuestion>(config =>
    {
        config.Parent<ContestItem>(content => content.ContestItemId);
    });
});
除了根节点之外,都需要配置向上查找的主键 Id,例如活动项需要向上查找教研活动,需要配置 config.Parent<Contest>(contestItem => contestItem.ContestId) 程序根据 ContestId 去查找是哪个教研活动。
如果需要显示多层目录,比如
教研活动:第一届教师节
活动项:数学教师活动项
则需要配置 SetAliasAndTitle。
下面这行代码的意思是设置别名(活动项),设置标题,从查找出来的实体中取 ContestItemName 作为标题。
config.SetAliasAndTitle("活动项", contestItem => contestItem.ContestItemName)
4.1 文件统计策略
目前实体中包含文件提供了两种策略进行统计。
- 
RichTextStatisticsStrategy富文本统计策略,用于文件信息存储在string类型的属性中。
- 
EntityHasFileIdStatisticsStrategy实体存储了所用文件的 Id。需要配合IHasFileIdObject接口一起使用(或者实体属性中包含)。FileId查找,未实现
使用AddHasUploadContentEntity 默认配置是 RichTextStatisticsStrategy,可通过泛型方式使用其他策略,如下所示:
Configure<FileServiceOptions>(options =>
{
    options.AddEntityConfig<BeiKe>(config =>
        config.Root("二次备课", design => design.Name));
    options.AddHasUploadContentEntity<BeiKeFile, EntityHasFileIdStatisticsStrategy>(config =>
        config.Parent<BeiKe>(bkFile => bkFile.BeiKeId));
});
4.2 多主键实体
比如上面策略的代码中 BeiKeFile 实体是多主键的。
请注意,如果多主键实体要配置文件统计,必须保证所有主键都是 Guid 类型的,且该实体必须位于叶子末尾,否则在查找实体时,会发生异常和意料之外的问题。
5. 多租户问题
项目虽然是按多租户开发的,但是存在大量跨租户的操作,所以需要注意租户的数据问题。不同租户需要在同一数据库下存储,切勿进行多租户多数据库的模式。
6. 测试问题
由于 Abp 测试是按照依赖的模块加载的,可能会出现越高层的模块测试速度越慢的情况(也与现在的模块未完全分离和共享数据库上下文有关),因为需要加载依赖模块的服务和数据库及种子数据等。
P.S. 测试中,每个方法都是独立运行的,所以每个方法都会进行一次初始化。
投票
代码结构
├─ Directory.Build.props(通用的配置默认给所有项目加载)
├─ Files(文件上传相关)
├─ Menu(权限菜单相关)
├─ NuGet.Config(Nuget配置)
├─ VoteBase(基础模块、包含选手、选手列表、活动、排行、报名等)
├─ VoteLiWu(收费模式、包含装饰品、支付、包装后的选手列表等)
├─ WeChat(微信交互相关)
├─ common.props(通用配置)
各种地址
- admin:admin1q2w3E*
注意事项
1. 自定义投票规则验证提供者
通过继承 RuleValidationProvider 抽象类快速实现投票规则验证。例如,一天只有特定时间才能进行投票
public class LimitTimeRuleValidationProvider : RuleValidationProvider
{
    public const string ProviderName = "LimitRangeTime";
    public const string StartTime = nameof(StartTime);
    public const string EndTime = nameof(EndTime);
    public LimitTimeRuleValidationProvider(IServiceProvider serviceProvider) : base(serviceProvider)
    {
    }
    public override string Name => ProviderName;
    public override Task ValidateAsync(RuleValidationContext context, CancellationToken cancellationToken = default)
    {
        var startTime = TimeOnly.FromDateTime(context.GetProperty<DateTime>(StartTime));
        var endTime = TimeOnly.FromDateTime(context.GetProperty<DateTime>(EndTime));
        if (startTime > endTime)
        {
            Logger.LogWarning($"因开始时间({startTime})大于结束时间({endTime}),跳过此验证!");
            return Task.CompletedTask;
        }
        var currentTime = TimeOnly.FromDateTime(DateTime.Now);
        if (currentTime > startTime && currentTime < endTime)
        {
            throw new UserFriendlyException($"当前时间段({startTime}-{endTime})不允许投票!");
        }
        return Task.CompletedTask;
    }
}
之后,创建一个继承RuleDefinitionProvider的类,如下所示
public class BaseRuleDefinitionProvider : RuleDefinitionProvider
{
    public override void Define(IRuleDefinitionContext context)
    {
        var group = context.AddGroup("Rule.Common.Group", "通用模块规则组");
        group
            .AddRuleDefinition(
                name: LimitTimeRuleValidationProvider.ProviderName,
                displayName: "禁止投票时间段")
            .WithProperty(
                propertyName: LimitTimeRuleValidationProvider.StartTime,
                defaultValue: DateTime.Parse("00:00:00"))
            .WithProperty(
                propertyName: LimitTimeRuleValidationProvider.EndTime,
                defaultValue: DateTime.Parse("06:00:00"));
    }
}
你需要在 Define 方法中添加你的规则验证定义提供者,并添加相关名称及默认值。
参考 Sanhe.Vote.Base.Application.Admin.Rules.Providers。
2. 注意规则中限制 IP 和地址
规则中限制 IP 和限制地址投票并没有进行过充分的测试。
而且获取的地址方式是通过第三方接口利用 IP 来获取的,比较依赖外部,充满了不确定性。
3. 选手列表缓存
现在选手列表缓存为了更好更快的完成业务,是将整个活动下所有选手一起缓存的。请注意选手参与人数与活动热度,如果这俩个点过高,可能会造成内存不足的问题。