762 lines
24 KiB
Markdown
762 lines
24 KiB
Markdown
|
|
k# 描述
|
|||
|
|
1. 部署环境 :Window Server
|
|||
|
|
2. 开发平台 :VS2015或VS2017
|
|||
|
|
3. ORM :SqlSugar
|
|||
|
|
4. Ioc框架 :Unity
|
|||
|
|
5. Aop框架 :Unity
|
|||
|
|
6. Json框架 :Newtonsoft.Json
|
|||
|
|
7. 日志框架 : log4net
|
|||
|
|
8. 缓存 :内存或Redis
|
|||
|
|
9. Api接口帮助:/swagger/,已按Area区域划分
|
|||
|
|
|
|||
|
|
## 通用操作
|
|||
|
|
1. 明显能预知的异常,使用BaseException抛出
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
if (workprocess.Status != (int)WorkProcessStatus.Stop) {throw new BaseException("当前进程状态不是停止");}
|
|||
|
|
```
|
|||
|
|
2. 写日志
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
LogHelper.Error("错误测试str");
|
|||
|
|
LogHelper.Info("错误测试Exception");
|
|||
|
|
```
|
|||
|
|
错误日志存放在目录App_Log,当异常发生的时候,每一层包括仓储和服务层都会拦截到错误信息,可以看到每一层具体的传参情况。看日志可以通过http请求txt文件查看到。
|
|||
|
|
|
|||
|
|
3.AOP事务
|
|||
|
|
|
|||
|
|
方法头添加[TransactionCallHandler],需要注意只拦截一个方法最外层的事务,一个方法只拦截一次。比如
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[TransactionCallHandler]
|
|||
|
|
public void RestartStopProcess(Guid workprocessId) {}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4、关于批量操作
|
|||
|
|
|
|||
|
|
在Controller层循环,在Service层只写单个处理的方法。比如例子,批量审核:
|
|||
|
|
Controller层:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[HttpPost]
|
|||
|
|
public void Audit(List<Guid> ids)
|
|||
|
|
{
|
|||
|
|
foreach(var id in ids) { _workProcessService.Audit(id);}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
Service层:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[TransactionCallHandler]
|
|||
|
|
public void Audit(Guid id) {}
|
|||
|
|
```
|
|||
|
|
此种方式能保证Service运用到事务,且不会出现大事务回滚的复杂情况。
|
|||
|
|
|
|||
|
|
5、关于事务补充
|
|||
|
|
|
|||
|
|
第三方不可逆操作在方法里需要写到最后一步,比如:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[TransactionCallHandler]
|
|||
|
|
public void TransferMoney()
|
|||
|
|
{
|
|||
|
|
1.执行保存数据库操作,更新支付状态
|
|||
|
|
2.执行微信打款操作
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
6、SQL注入
|
|||
|
|
|
|||
|
|
若对SQL注入不了解,没有把握自己写的方法会不会导致SQL注入,重点看下面的SQL操作demo,所有的SQL请求参数都采用参数化查询的方式提交。
|
|||
|
|
|
|||
|
|
7、缓存
|
|||
|
|
缓存放弃使用Aop方式实现,因方法内部调用时会导致无法拦截。现使用缓存直接使用接口:IMyCodeCacheService中的Get和Set方法,比如:
|
|||
|
|
```
|
|||
|
|
private readonly IMyCodeCacheService _myCodeCacheService;
|
|||
|
|
public UserService(IMyCodeCacheService myCodeCacheService)
|
|||
|
|
{
|
|||
|
|
_myCodeCacheService = myCodeCacheService;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public string GetRegionName(Guid id)
|
|||
|
|
{
|
|||
|
|
var cacheKey = $"region-{id}";
|
|||
|
|
var cacheValue = _myCodeCacheService.Get(cacheKey);
|
|||
|
|
if(cacheValue == null)
|
|||
|
|
{
|
|||
|
|
//获取得到数据
|
|||
|
|
var data = "广东";
|
|||
|
|
_myCodeCacheService.Set(cacheKey,data,new TimeSpan(1,0,1));
|
|||
|
|
return data;
|
|||
|
|
}
|
|||
|
|
return cacheValue.ToString();
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
8、Token授权
|
|||
|
|
|
|||
|
|
头部增加:Authorization token,比如:
|
|||
|
|
header 'Authorization: CbkmWDbwqf5BGvRUA416zYRBlAM09Py_mYGXDwoFflEqsHworu'
|
|||
|
|
|
|||
|
|
9、不需要授权的接口,在Controller层方法添加头:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[AllowAnonymous]
|
|||
|
|
```
|
|||
|
|
比如:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
[HttpGet]
|
|||
|
|
[AllowAnonymous]
|
|||
|
|
public void ExportTest()
|
|||
|
|
{
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
10、接口统一返回参数,格式:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
{
|
|||
|
|
"Data": null,//返回如果有数据,统一放这里,不管是List还是单实体
|
|||
|
|
"ResultCode": 1,//如果接口没出错,则返回1;返回-1则接口出错
|
|||
|
|
"ErrorMessage": null//如果接口出错,这里显示出错信息
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
11、GenerateCode
|
|||
|
|
|
|||
|
|
代码自动生成,如果已存在相应的文件,除了实体外,仓储是直接跳过。
|
|||
|
|
|
|||
|
|
### 代码目录结构
|
|||
|
|
```
|
|||
|
|
├── 00-Lib -- 依赖库文件夹
|
|||
|
|
├── 02-Document -- 文档文件夹
|
|||
|
|
├── 03-FrameWork -- 存放第三方开源源代码
|
|||
|
|
├── 04-MyCode.Project.Domain --域
|
|||
|
|
│ ├── Config --配置实体
|
|||
|
|
│ ├── Message --消息实体
|
|||
|
|
│ │ ├── Act --操作请求实体
|
|||
|
|
│ │ ├── Request --查询实体
|
|||
|
|
│ │ ├── Response --响应实体
|
|||
|
|
│ │ ├── Model --数据库实体
|
|||
|
|
│ │ ├── Repositories --仓储接口
|
|||
|
|
├── 05-MyCode.Project.GenerateCode --代码生成
|
|||
|
|
├── 06-MyCode.Project.Infrastructure --基础设施层
|
|||
|
|
├── 07-MyCode.Project.OutSideService --外部引用层
|
|||
|
|
├── 08-MyCode.Project.Repositories --仓储实现层
|
|||
|
|
├── 09-MyCode.Project.ScheduleTask --调度层
|
|||
|
|
├── 10-MyCode.Project.Services --服务层
|
|||
|
|
├── 11-MyCode.Project.WebApi --WEBAPI层 --
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
## SQL操作
|
|||
|
|
|
|||
|
|
1. Insert插入成功会自动将自递增id新的值更新到实体
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.Add(new WorkProcess()
|
|||
|
|
{
|
|||
|
|
FuncType = 1,
|
|||
|
|
MethodName = "",
|
|||
|
|
ParameterInfo = "",
|
|||
|
|
WorkProcessId = Guid.NewGuid(),
|
|||
|
|
UpdateTime = DateTime.Now
|
|||
|
|
});
|
|||
|
|
```
|
|||
|
|
2. 取单表的前n条数据,单表情况多参考这里写法,只返回需要的字段,不要返回表中所有字段;
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
return _workProcessRepository.Queryable()
|
|||
|
|
.Where(p => p.Status == (int)WorkProcessStatus.RUUNING && p.SystemType == 200)
|
|||
|
|
.Take(top)
|
|||
|
|
.OrderBy(p => p.UpdateTime).Select(p => new { p.WorkProcessId }).ToList();
|
|||
|
|
|
|||
|
|
//生成Sql
|
|||
|
|
exec sp_executesql N'SELECT * FROM (SELECT [WorkProcessId] AS [WorkProcessId] ,ROW_NUMBER() OVER(ORDER BY [UpdateTime] ASC) AS RowIndex FROM [WorkProcess] WITH(NOLOCK) WHERE (( [Status] = @Status0 ) AND ( [SystemType] = @Const1 ))) T WHERE RowIndex BETWEEN 1 AND 10',N'@Status0 int,@Const1 int',@Status0=1,@Const1=200
|
|||
|
|
```
|
|||
|
|
3. Update 更新
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
var workProcess = _workProcessRepository.SelectFirst(p => p.WorkProcessId == Guid.Parse("7BDDBBD3-B1CD-4C25-93BA-D7BF22032108"));
|
|||
|
|
workProcess.Remark = "修改测试";
|
|||
|
|
_workProcessRepository.Update(workProcess);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
4. Update 更新部分字段,如果能明确更新字段,用该方法
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.Update(
|
|||
|
|
it => new WorkProcess { Remark = "测试批量修改",SystemType = 0 },
|
|||
|
|
p => p.WorkProcessId ==Guid.Parse("7BDDBBD3-B1CD-4C25-93BA-D7BF22032108"));
|
|||
|
|
//生成Sql
|
|||
|
|
exec sp_executesql N'UPDATE [WorkProcess] SET
|
|||
|
|
[Remark] = @Const0 , [SystemType] = @Const1 WHERE ( [WorkProcessId] =@constant2)',N'@Const0 nvarchar(4000),@Const1 int,@constant2 uniqueidentifier',@Const0=N'测试批量修改',@Const1=0,@constant2='7BDDBBD3-B1CD-4C25-93BA-D7BF22032108'
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
5.Sql语句查询返回列表
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.SelectList<WorkProcess>("Select top 10 * from workprocess where systemtype=@systemtype", new { SystemType = 200 });
|
|||
|
|
//生成的Sql
|
|||
|
|
exec sp_executesql N'Select top 10 * from workprocess where systemtype=@systemtype',N'@SystemType int',@SystemType=200
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
6.根据一组主键Guid返回列表
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
var ids = new List<Guid>();
|
|||
|
|
ids.Add(Guid.Parse("6B2E752C-CBD3-4C56-80C0-0000339F982A"));
|
|||
|
|
ids.Add(Guid.Parse("E1CD8853-993C-4EFE-8863-0000FDF68054"));
|
|||
|
|
_workProcessRepository.SelectList(ids);
|
|||
|
|
|
|||
|
|
//生成的Sql语句
|
|||
|
|
SELECT [WorkProcessId],[TypePath],[MethodName],[ParameterInfo],[UpdateTime],[Remark],[Status],[ExceptionInfo],[SystemType],[FuncType] FROM [WorkProcess] WITH(NOLOCK) WHERE [WorkProcessId] IN ('6b2e752c-cbd3-4c56-80c0-0000339f982a','e1cd8853-993c-4efe-8863-0000fdf68054')
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
7.大数据插入
|
|||
|
|
|
|||
|
|
当需要插入非常多的数据时,先将数据处理好,调用Add(List)的方法,速度会很快;特别注意不要一个个去Add(Model),这种会非常慢;
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
var workProcessList = new List<WorkProcess>();
|
|||
|
|
|
|||
|
|
for (int i = 0; i < 1000; i++)
|
|||
|
|
{
|
|||
|
|
workProcessList.Add(new WorkProcess(){
|
|||
|
|
FuncType = 1,
|
|||
|
|
MethodName = "",
|
|||
|
|
ParameterInfo = "",
|
|||
|
|
WorkProcessId = Guid.NewGuid(),
|
|||
|
|
UpdateTime = DateTime.Now
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
_workProcessRepository.Add(workProcessList);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
8.大数据修改
|
|||
|
|
|
|||
|
|
当需要修改非常多的数据时,先将数据处理好,调用Update(List)的方法,速度非常快,特别注意不要一个个去Update(Model),这种会非常慢;另提供一个优化的方法,只批量更新需要的字段:
|
|||
|
|
```
|
|||
|
|
//lines为一组Guid集合,这里某些情况并不需要从数据库拿数据
|
|||
|
|
var workprocesses = lines.Select(line => new WorkProcess { WorkProcessId = line.WorkProcessId, TypePath = "MyCode" });
|
|||
|
|
|
|||
|
|
_workProcessRepository.Update(workprocesses,it => new { it.TypePath});
|
|||
|
|
//生成的Sql
|
|||
|
|
UPDATE S SET S.[TypePath]=T.[TypePath] FROM [WorkProcess] S INNER JOIN (
|
|||
|
|
|
|||
|
|
SELECT N'6b2e752c-cbd3-4c56-80c0-0000339f982a' AS [WorkProcessId],N'test201898' AS [TypePath]
|
|||
|
|
UNION ALL
|
|||
|
|
SELECT N'e1cd8853-993c-4efe-8863-0000fdf68054' AS [WorkProcessId],N'test201898' AS [TypePath]
|
|||
|
|
) T ON S.[WorkProcessId]=T.[WorkProcessId]
|
|||
|
|
```
|
|||
|
|
9、使用Sql语句查询单条数据
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.SelectFirst<WorkProcess>("Select top 1 * from workprocess")
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
带参数:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_sysLoginRepository.SelectList<SysLogin>("select top 10 * from SysLogin where login like '%' + @key + '%'", new { key = "k" });
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
|
|||
|
|
10、使用Sql语句查询列表数据
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.SelectList<WorkProcess>("Select top 10 * from workprocess");
|
|||
|
|
```
|
|||
|
|
11、IN查询
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.Queryable().In<WorkProcess>("workprocessid", "6B2E752C-CBD3-4C56-80C0-0000339F982A", "E1CD8853-993C-4EFE-8863-0000FDF68054")
|
|||
|
|
```
|
|||
|
|
根据主键IN查询:
|
|||
|
|
```
|
|||
|
|
_basCourseRepository.Queryable().In(courseIds).Select(p => new KeyValue { Text = p.Name, Value = p.Id }).ToList();
|
|||
|
|
```
|
|||
|
|
```
|
|||
|
|
_basDataTagRepository.Queryable().In(it => it.data_id, projectIds);
|
|||
|
|
```
|
|||
|
|
12、Count查询
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
_workProcessRepository.Count(p => p.SystemType == 200);
|
|||
|
|
```
|
|||
|
|
13、分页
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
var strSql = $@"SELECT
|
|||
|
|
L.LoginId,
|
|||
|
|
Login,
|
|||
|
|
L.Name,
|
|||
|
|
Tele,
|
|||
|
|
L.Editor,
|
|||
|
|
L.EditTime,
|
|||
|
|
L.Status,
|
|||
|
|
SR.Name AS RoleName
|
|||
|
|
FROM
|
|||
|
|
SysLogin L WITH (nolock)
|
|||
|
|
LEFT JOIN
|
|||
|
|
dbo.SysLoginRole LR WITH (NOLOCK) ON L.LoginID = LR.LoginID
|
|||
|
|
LEFT JOIN
|
|||
|
|
dbo.SysRole SR WITH (nolock) ON LR.RoleID = SR.RoleID";
|
|||
|
|
|
|||
|
|
var where = new SearchCondition();
|
|||
|
|
where.AddCondition("L.MerchantID",merchantId, SqlOperator.Equal,true);
|
|||
|
|
//where.AddSqlCondition("L.Login like '%' + @key + '%'",
|
|||
|
|
true,
|
|||
|
|
new SugarParameter("key", "a"));
|
|||
|
|
where.AddSqlCondition("a.nick like @key or a.true_name like @key or a.mobile like @key ",!string.IsNullOrWhiteSpace(con.KeyWord),new SqlSugar.SugarParameter("key",$"%{con.KeyWord}%"));
|
|||
|
|
|
|||
|
|
where.AddCondition("L.status",1,SqlOperator.Equal,true);
|
|||
|
|
|
|||
|
|
var whereResult = where.BuildConditionSql();
|
|||
|
|
|
|||
|
|
return this.SelectListPage<LoginListResp>(strSql, where, request.Page, request.PageSize, "EditTime desc");
|
|||
|
|
```
|
|||
|
|
14、按需读取
|
|||
|
|
```
|
|||
|
|
//这里只需要一个字段
|
|||
|
|
var loginRole = _sysLoginRoleRepository.Queryable().Where(p => p.LoginID == loginId).Select(p => new { p.RoleID }).First();
|
|||
|
|
```
|
|||
|
|
15、增加支持Sql二级缓存,关键字WithCache
|
|||
|
|
```
|
|||
|
|
var weconfig = _basWechatConfigRepository.Queryable().Where(p => p.Appid == request.AppId).Select(p => new { p.Appid, p.AppSecret,p.CompanyId }).WithCache(60).First();
|
|||
|
|
```
|
|||
|
|
16、支持以工作单元方式一次性执行打包的Sql,带事务;
|
|||
|
|
```
|
|||
|
|
_basMemberRepository.AddQueue(insertMember);
|
|||
|
|
_basMemberMoreRepository.AddQueue(memberMore);
|
|||
|
|
_basMemberRepository.SaveQueue();
|
|||
|
|
//也可以用_basMemberMoreRepository.SaveQueue();
|
|||
|
|
```
|
|||
|
|
工作单元的此种方法唯一不足是默认mysql没有加锁,如果有修改,则并发会出现问题。而mysql的for update语句可以保证update该行或表数据的时候,没有被其它事务占用。如果其它事务占用,则会等待其它事务提交再提交。所以上面的语句可以写成:
|
|||
|
|
```
|
|||
|
|
_basMemberMoreRepository.BeginTran();
|
|||
|
|
var memberMore = _basMemberMoreRepository.Queryable().Where(p => p.Id == 2005111149137091).With(SqlWith.UpdLock).First();
|
|||
|
|
_basMemberRepository.AddQueue(insertMember);
|
|||
|
|
_basMemberMoreRepository.UpdateQueue(memberMore);
|
|||
|
|
_basMemberRepository.SaveQueue();//内部实现try,catch风格的事务和回滚,且已经处理事务嵌套
|
|||
|
|
//也可以用_basMemberMoreRepository.SaveQueue();
|
|||
|
|
```
|
|||
|
|
17、Api缓存
|
|||
|
|
```
|
|||
|
|
Api缓存为在控制层使用,使用例子为:
|
|||
|
|
[ApiCache(ServiceSecond=50)]
|
|||
|
|
[HttpGet]
|
|||
|
|
public DateTime GetTest(int i)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
# 参考或引用
|
|||
|
|
|
|||
|
|
- https://github.com/sunkaixuan/SqlSugar
|
|||
|
|
- http://jwt.io
|
|||
|
|
- https://github.com/jianxuanbing/SwashbuckleEx
|
|||
|
|
|
|||
|
|
# 更新记录 2020-5-10
|
|||
|
|
1. SqlSugar升级到最新版
|
|||
|
|
2. 增加ToJson扩展
|
|||
|
|
3. 日志Info和Error存放不同目录
|
|||
|
|
4. Connection关闭改为按PerRequest关闭
|
|||
|
|
5. 实体生成增加字段名映射
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
http://120.78.137.110:2343
|
|||
|
|
1) 需要更换负载均衡为nginx,报表;
|
|||
|
|
2)改名D:\publish\LxmReportApi,统一使用lxm-report-api里面的
|
|||
|
|
|
|||
|
|
1) StaMemberExpensesRecord波哥需要补全这个表的历史数据
|
|||
|
|
|
|||
|
|
|
|||
|
|
2) 判断金额是否一致
|
|||
|
|
select a.id,((a.total_amount - a.balance)*a.direct) '主表' ,
|
|||
|
|
ROUND(b.amont,2) '明细表',create_time
|
|||
|
|
from lxm_sheet a inner join
|
|||
|
|
( select sales_sheet_id,
|
|||
|
|
SUM( no_balance_pay_unit_price * qty * direct ) amont from
|
|||
|
|
lxm_sheet_item GROUP BY sales_sheet_id )
|
|||
|
|
b on a.id=b.sales_sheet_id
|
|||
|
|
where (a.total_amount - a.balance)*a.direct<>ROUND(b.amont,2) ORDER BY create_time
|
|||
|
|
|
|||
|
|
|
|||
|
|
select * from (
|
|||
|
|
select a.id,((a.total_amount - a.balance)) '主表' ,
|
|||
|
|
ROUND(b.amont,4) '明细表',create_time
|
|||
|
|
from lxm_sheet a inner join
|
|||
|
|
( select sales_sheet_id,
|
|||
|
|
SUM( no_balance_pay_unit_price * qty ) amont from
|
|||
|
|
lxm_sheet_item GROUP BY sales_sheet_id )
|
|||
|
|
b on a.id=b.sales_sheet_id
|
|||
|
|
where (a.total_amount - a.balance)*a.direct<>ROUND(b.amont,4)
|
|||
|
|
) t where (t.主表 - t.明细表) > 0.01
|
|||
|
|
|
|||
|
|
|
|||
|
|
//MSSQL那边的验证金额
|
|||
|
|
select sum(TotalAmount-balance+rechargeamount) from StaMemberExpensesRecord a
|
|||
|
|
where shopid='43d7c3d0-8501-19e9-b3ac-39ef10cc301a' and createtime>='2023-04-01' and a.createtime<'2023-05-01'
|
|||
|
|
and sheettYPE <> 5
|
|||
|
|
and flag = 100
|
|||
|
|
|
|||
|
|
|
|||
|
|
//比对价格不一致
|
|||
|
|
select a.id,a.no_balance_pay_amount,a.balance,b.pay_amount from lxm_sheet a
|
|||
|
|
left join(
|
|||
|
|
select sales_sheet_id,sum(no_balance_pay_amount*direct) pay_amount from lxm_sheet_item
|
|||
|
|
group by sales_sheet_id
|
|||
|
|
) b on a.id = b.sales_sheet_id
|
|||
|
|
where shop_id='43d7c3d0-8501-19e9-b3ac-39ef10cc301a' and create_time>='2023-04-01' and create_time<'2023-05-01'
|
|||
|
|
and a.no_balance_pay_amount != b.pay_amount
|
|||
|
|
|
|||
|
|
select sum(totalamount-balance) from StaMemberExpensesRecord a
|
|||
|
|
where shopid='43d7c3d0-8501-19e9-b3ac-39ef10cc301a' and createtime>='2023-04-01' and a.createtime<'2023-05-01'
|
|||
|
|
and sheettYPE <> 5 and rechargeamount = 0
|
|||
|
|
and flag = 100
|
|||
|
|
1465.8000
|
|||
|
|
|
|||
|
|
select sum(rechargeamount) from StaMemberExpensesRecord a
|
|||
|
|
where shopid='43d7c3d0-8501-19e9-b3ac-39ef10cc301a' and createtime>='2023-04-01' and a.createtime<'2023-05-01'
|
|||
|
|
and sheettYPE <> 5 and rechargeamount > 0
|
|||
|
|
and flag = 100
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 计算业绩汇总不一致的情况
|
|||
|
|
select
|
|||
|
|
a.id,
|
|||
|
|
a.direct * a.total_amount,b.amount from lxm_sheet a
|
|||
|
|
inner join (
|
|||
|
|
select
|
|||
|
|
sales_sheet_id,
|
|||
|
|
SUM(in_blance_amount*direct) amount
|
|||
|
|
from lxm_sheet_item
|
|||
|
|
GROUP BY sales_sheet_id
|
|||
|
|
) b on a.id = b.sales_sheet_id
|
|||
|
|
where abs(a.direct * a.total_amount - b.amount) >0.1
|
|||
|
|
|
|||
|
|
|
|||
|
|
select * from (
|
|||
|
|
select
|
|||
|
|
a.id,
|
|||
|
|
a.no_balance_pay_amount*direct as '主表' ,
|
|||
|
|
ROUND(b.amont,2) '明细表',
|
|||
|
|
-- ((a.no_balance_pay_amount)*a.direct)-ROUND(b.amont,2),
|
|||
|
|
create_time
|
|||
|
|
from
|
|||
|
|
lxm_sheet a inner join
|
|||
|
|
( select sales_sheet_id,
|
|||
|
|
SUM( no_balance_pay_amount * direct ) amont from
|
|||
|
|
lxm_sheet_item GROUP BY sales_sheet_id )
|
|||
|
|
b on a.id=b.sales_sheet_id
|
|||
|
|
where ((a.no_balance_pay_amount)*a.direct)<>ROUND(b.amont,2)
|
|||
|
|
) t where ABS(t.`主表`-t.`明细表`)>1 ORDER BY create_time DESC
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
名词解释:
|
|||
|
|
|
|||
|
|
业绩:销售单+充值单,加上余额,MSSQL中就是明细中的CalPrice,这里没有预售单是因为预售单最终也是转成了销售单,而算业绩是以转换成销售单时间来算的。
|
|||
|
|
收款:实际支付金额,预售+销售+充值,预转销的时候并不需要实际支付钱,但并不是所有销售单都是预转销订单,需要去掉余额。
|
|||
|
|
|
|||
|
|
CalPrice:含余额支付的单价
|
|||
|
|
NcCalPrice:不含余额支付的单价
|
|||
|
|
|
|||
|
|
门店诊断:为0有可能显示也有可能不显示,如果营业中但业绩为0,则显示为0;
|
|||
|
|
|
|||
|
|
2023-6-19
|
|||
|
|
update lxm_sheet_item
|
|||
|
|
set has_child_node = 0
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
1) 套装商品,统计套装SKU,不需要按明细统计;
|
|||
|
|
barcodeId = 'c12c74cf-8bf4-667d-877f-3a01634de171'
|
|||
|
|
|
|||
|
|
2) 赠送的不参与统计,销售。赠送的是放其它表。-可以暂时不考虑
|
|||
|
|
|
|||
|
|
3) 卡券不参与商品或者服务的统计,只参与业绩金额的统计
|
|||
|
|
|
|||
|
|
4) 自定义套餐,商品需要拆,服务也需要,如果出现没有服务的情况,则不需要拆,直接套餐就是服务了 BusSalesServiceSetLog
|
|||
|
|
-- 均摊价格
|
|||
|
|
|
|||
|
|
按吊牌价均摊,最后补齐,单价4为小数,4舍5入,小计两位小数4舍五入
|
|||
|
|
|
|||
|
|
5) 组合商品目前下单是直接拆成明细的,所以没这个问题;
|
|||
|
|
|
|||
|
|
6) 服务套餐 - 经2023-6-16和阿俊确认,不需要拆分到底部
|
|||
|
|
|
|||
|
|
如果需要统计的是分类的数据,则要统计到明细后细分的数据,条件:lxm_sheet_item.has_child_node=0(没有子节点即为所有叶子节点数据)
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
1) 自定义套餐旧的不动,算到服务;
|
|||
|
|
|
|||
|
|
|
|||
|
|
### 使用天数MSSQL那边的数据
|
|||
|
|
select
|
|||
|
|
|
|||
|
|
convert(varchar(10),a.createtime,120)
|
|||
|
|
from
|
|||
|
|
RpShopUseAppletData a with(nolock)
|
|||
|
|
left join
|
|||
|
|
|
|||
|
|
bascustomer c with(nolock) on c.id = a.customerid
|
|||
|
|
where UseType in (30, 31, 32, 33, 301, 302, 303) and a.ShopID = '51870d25-999f-e48f-5523-39e64165db3f' and a.createtime >= '2023-6-1' and a.createtime <= '2023-7-1'
|
|||
|
|
group by convert(varchar(10),a.createtime,120)
|
|||
|
|
|
|||
|
|
union
|
|||
|
|
SELECT
|
|||
|
|
convert(varchar(10),a.createtime,120)
|
|||
|
|
FROM
|
|||
|
|
StaMemberExpensesRecord a with(nolock)
|
|||
|
|
left join
|
|||
|
|
BasCustomer c on a.CustomerId = c.ID
|
|||
|
|
WHERE a.ShopId IS NOT NULL AND a.shopid='51870d25-999f-e48f-5523-39e64165db3f'
|
|||
|
|
and a.createtime >= '2023-6-1' and a.createtime < '2023-7-1'
|
|||
|
|
group by convert(varchar(10),a.createtime,120)
|
|||
|
|
|
|||
|
|
MYSQL对比两边的数据差异
|
|||
|
|
select a.shop_id,a.shi_yong_tian_shu,b.usedays from rp_shop_zhenduansibiao_search a
|
|||
|
|
JOIN (select shop_id, COUNT(1) usedays from lxm_use_days where create_time>='2023-07-01' GROUP BY shop_id ) b
|
|||
|
|
on a.shop_id=b.shop_id where a.days='2023-07-01' and a.shi_yong_tian_shu<>b.usedays
|
|||
|
|
|
|||
|
|
|
|||
|
|
1) 在该店铺以前没有单次消费500元以上?
|
|||
|
|
只算当前店铺。这个以前是根据查询条件确定的时间,还是和查询时间无关的,和查询条件无关;
|
|||
|
|
|
|||
|
|
2)这种新客老客意思是这个月只要有一笔满足,那么这个月这个人所有消费都算是老客是吗?还是说满足之后才算老客。
|
|||
|
|
比如这个月1号,我消费了100元,
|
|||
|
|
2号的时候,我消费了600元。
|
|||
|
|
3号的时候,我消费了50元。
|
|||
|
|
那么这个是算老客消费了750元?
|
|||
|
|
第一个月满足了条件的话,算是新客,下个月开始就算老客;
|
|||
|
|
|
|||
|
|
3) 总业绩
|
|||
|
|
|
|||
|
|
9月 总业绩1000
|
|||
|
|
|
|||
|
|
分别是 :
|
|||
|
|
1号A老客消费了900
|
|||
|
|
2号B新客消费了300
|
|||
|
|
3号C新客退了100
|
|||
|
|
4号A老客退了100
|
|||
|
|
|
|||
|
|
|
|||
|
|
检查数据
|
|||
|
|
select * from
|
|||
|
|
((select
|
|||
|
|
a.Id,
|
|||
|
|
a.direct,
|
|||
|
|
a.total_amount,
|
|||
|
|
a.create_time,
|
|||
|
|
a.member_id,
|
|||
|
|
a.shop_id,
|
|||
|
|
more.is_first_more_than_500,
|
|||
|
|
more.remark,
|
|||
|
|
more.update_time,
|
|||
|
|
more.is_old_member_500,
|
|||
|
|
more.first_more_than_500_sheet_id
|
|||
|
|
from
|
|||
|
|
lxm_sheet a
|
|||
|
|
inner join
|
|||
|
|
lxm_sheet_more more on a.id = more.id
|
|||
|
|
where a.shop_id='f344cd45-26ae-3ea1-0cdd-39e611e31484' and a.create_time >= '2020-8-1' and a.create_time < '2020-9-1'
|
|||
|
|
)
|
|||
|
|
UNION
|
|||
|
|
(select
|
|||
|
|
a.Id,
|
|||
|
|
a.direct,
|
|||
|
|
a.amount,
|
|||
|
|
a.create_time,
|
|||
|
|
a.member_id,
|
|||
|
|
a.shop_id,
|
|||
|
|
more.is_first_more_than_500,
|
|||
|
|
more.remark,
|
|||
|
|
more.update_time,
|
|||
|
|
more.is_old_member_500,
|
|||
|
|
more.first_more_than_500_sheet_id
|
|||
|
|
from
|
|||
|
|
lxm_recharge a
|
|||
|
|
inner join
|
|||
|
|
lxm_sheet_more more on a.id = more.id
|
|||
|
|
where a.shop_id='f344cd45-26ae-3ea1-0cdd-39e611e31484' and a.create_time >= '2020-8-1' and a.create_time < '2020-9-1'
|
|||
|
|
)) T order by T.create_time asc
|
|||
|
|
|
|||
|
|
|
|||
|
|
select * from
|
|||
|
|
select
|
|||
|
|
TT.shop_id,
|
|||
|
|
TT.year,
|
|||
|
|
TT.`month`,
|
|||
|
|
sum(case when TT.member_id is not null and TT.is_first_more_than_500 = 0 and TT.is_old_member_500 = 0 then TT.total_amount else 0 end ) ThisMonthTiYanAmount,
|
|||
|
|
sum(case when TT.member_id is not null and TT.is_first_more_than_500 = 0 and TT.is_old_member_500 = 0 then 1 else 0 end ) ThisMonthTiYanQty,
|
|||
|
|
sum(case when TT.is_old_member_500 = 1 then TT.total_amount else 0 end) ThisMonthOldMemberAmount,
|
|||
|
|
sum(case when TT.is_old_member_500 = 1 then 1 else 0 end ) ThisMonthOldMemberQty,
|
|||
|
|
sum(case when TT.is_first_more_than_500 = 1 then TT.total_amount else 0 end) ThisMonthNewMemberAmount,
|
|||
|
|
sum(case when TT.is_first_more_than_500 = 1 then 1 else 0 end ) ThisMonthNewMemberQty,
|
|||
|
|
sum(TT.not_member_order_qty) ThisMonthNotMemberOrderQty,
|
|||
|
|
sum(case when TT.member_id is null then TT.total_amount else 0 end) ThisMonthNotMemberAmount
|
|||
|
|
from (
|
|||
|
|
select
|
|||
|
|
T.shop_id,
|
|||
|
|
T.member_id,
|
|||
|
|
YEAR(T.create_time) year,
|
|||
|
|
MONTH(T.create_time) month,
|
|||
|
|
max(T.is_old_member_500) is_old_member_500,
|
|||
|
|
max(T.is_first_more_than_500) is_first_more_than_500,
|
|||
|
|
sum(T.total_amount) total_amount,
|
|||
|
|
sum(T.no_balance_pay_amount) no_balance_pay_amount,
|
|||
|
|
sum(case when T.member_id is NULL and T.total_amount > 0 then 1 else 0 end) not_member_order_qty
|
|||
|
|
from (
|
|||
|
|
(select
|
|||
|
|
a.create_time,
|
|||
|
|
a.shop_id,
|
|||
|
|
a.member_id,
|
|||
|
|
total_amount * direct total_amount,
|
|||
|
|
no_balance_pay_amount * direct no_balance_pay_amount,
|
|||
|
|
more.is_first_more_than_500,
|
|||
|
|
more.is_old_member_500
|
|||
|
|
from
|
|||
|
|
lxm_sheet a
|
|||
|
|
inner JOIN
|
|||
|
|
lxm_sheet_more more on a.id = more.id
|
|||
|
|
)
|
|||
|
|
UNION
|
|||
|
|
(select
|
|||
|
|
a.create_time,
|
|||
|
|
a.shop_id,
|
|||
|
|
a.member_id,
|
|||
|
|
0 total_amount,
|
|||
|
|
amount no_balance_pay_amount,
|
|||
|
|
more.is_first_more_than_500,
|
|||
|
|
more.is_old_member_500
|
|||
|
|
from
|
|||
|
|
lxm_recharge a
|
|||
|
|
inner JOIN
|
|||
|
|
lxm_sheet_more more on a.id = more.id
|
|||
|
|
)
|
|||
|
|
) T
|
|||
|
|
group by T.shop_id,T.member_id,YEAR(T.create_time),MONTH(T.create_time)
|
|||
|
|
) TT
|
|||
|
|
group by TT.shop_id,TT.`year`,TT.`month`
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
select a.*,more.* from lxm_sheet a
|
|||
|
|
inner JOIN
|
|||
|
|
lxm_sheet_more more on a.id = more.id
|
|||
|
|
where a.shop_id = '0d953a13-aece-81e3-31c7-39e63b48b1f8' and a.member_id is null
|
|||
|
|
|
|||
|
|
select * from lxm_sheet_more
|
|||
|
|
where id = '009278e2-3143-fe99-5d8d-39e7ab3cc5ee'
|
|||
|
|
|
|||
|
|
|
|||
|
|
部署:
|
|||
|
|
1) 增加表lxm_sheet_more
|
|||
|
|
2) lxm_sheet和lxm_recharge增加member_id
|
|||
|
|
3) 导出增加两个模板文件
|
|||
|
|
|
|||
|
|
MSSQL那边的业绩获取:业绩:只统计销售订单的业绩
|
|||
|
|
SELECT SUM(record.TotalAmount+record.YuShouTotalAmount) FROM dbo.StaMemberExpensesRecord record WITH(NOLOCK)
|
|||
|
|
WHERE shopid = '55de1cfa-8bd7-ead4-2ec5-39e9da7379b8'
|
|||
|
|
and CreateTime >= '2023-7-1'
|
|||
|
|
and CreateTime < '2023-8-1'
|
|||
|
|
AND SheetType IN ( 1,4)
|
|||
|
|
|
|||
|
|
对应MYSQL的取业绩:
|
|||
|
|
select sum(total_amount*direct) from lxm_sheet
|
|||
|
|
where shop_id = '55de1cfa-8bd7-ead4-2ec5-39e9da7379b8' and create_time >= '2023-7-1' and create_time < '2023-8-1' and type = 1
|
|||
|
|
|
|||
|
|
|
|||
|
|
增加表:lxm_sheet_item_package
|
|||
|
|
|
|||
|
|
预转销的订单的sheet.NoBalancePayAmount = 0;
|
|||
|
|
|
|||
|
|
###
|
|||
|
|
1)增加表:lxm_enum表和数据,lxm_relation,lxm_sku,lxm_service
|
|||
|
|
2)检查lxm_sheet_more数据进入情况;
|
|||
|
|
3)更新订单改成20秒更新30秒内数据变化;
|
|||
|
|
4) 表lxm_sheet增加字段:
|
|||
|
|
is_porder_sorder : 1,是预转销订单 0,正常订单
|
|||
|
|
from_type: 0= 其他; 1= 抖音; 2= 美团; 3=自然流量; 4=老顾客列表 5=1元公益、派单
|
|||
|
|
is_new_member_order:1新客订单
|
|||
|
|
is_first_order:1是否首单
|
|||
|
|
member_mobile:会员手机
|
|||
|
|
order_clerk_id:下单店员id
|
|||
|
|
order_clerk_name:店员姓名
|
|||
|
|
order_clerk_mobile:店员手机
|
|||
|
|
5) 检查下 StaMemberExpensesRecord != 100的数据,
|
|||
|
|
6) 需要补充:is_new_member_order,is_first_order,clerk_id的数据。需要核对guid的新增字段是否为null值
|
|||
|
|
7) lxm_shop增加字段:status ,1:启用 0:禁用
|
|||
|
|
|
|||
|
|
|
|||
|
|
流行美单据:
|
|||
|
|
|
|||
|
|
-预售单 BusPreSalesSheet
|
|||
|
|
-销售单
|
|||
|
|
-- BusSalesSheet -> 有可能是预转销的订单
|
|||
|
|
-> 正常的订单
|
|||
|
|
-退款单
|
|||
|
|
|
|||
|
|
|
|||
|
|
|
|||
|
|
销售计划的排查:
|
|||
|
|
1)根据订单id得到下单店员
|
|||
|
|
select id,sheet,create_time,direct,order_clerk_id from lxm_sheet
|
|||
|
|
where id = '845c7b9b-d1b0-47ff-8591-01a57b9f13c0'
|
|||
|
|
|
|||
|
|
2) 根据店员ID,得到这个店员的销售计划 :
|
|||
|
|
select
|
|||
|
|
cPlan.id,-- 计划id
|
|||
|
|
cPlan.product_id,-- 套餐id
|
|||
|
|
cPlan.name,-- 套餐名
|
|||
|
|
pPlan.package_id source_package_id -- 来源的套餐id
|
|||
|
|
from bd_clerk_product_sales_plan cPlan
|
|||
|
|
left join
|
|||
|
|
lxm_product_sales_schedule pPlan on cPlan.Product_Id = pPlan.Id
|
|||
|
|
where cPlan.status = 1 and cPlan.version_time = '2023-11-1 0:00:00'
|
|||
|
|
and cPlan.clerk_id = '062dd030-194c-012a-df77-3a0cea5ada93'
|
|||
|
|
|
|||
|
|
3) 根据上面得到的套餐id,得到这些套餐的所有商品关系:
|
|||
|
|
|
|||
|
|
-- 得到套餐的所有商品
|
|||
|
|
select
|
|||
|
|
a.id Id,-- 套擦id
|
|||
|
|
a.package_name PackageName,
|
|||
|
|
sku.name ProductOrServiceName,
|
|||
|
|
sku.barcode_id ProductOrServiceId,
|
|||
|
|
r.data_type DataType,
|
|||
|
|
sku.base_price SourcePrice,
|
|||
|
|
r.qty Qty
|
|||
|
|
from
|
|||
|
|
lxm_relation r
|
|||
|
|
inner join
|
|||
|
|
lxm_product_sales_schedule a on r.main_id = a.id
|
|||
|
|
left join
|
|||
|
|
lxm_sku sku on r.sub_id = sku.barcode_id and r.data_type = 1
|
|||
|
|
where a.status = 1 and a.id in (
|
|||
|
|
--套餐id
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
4) 根据订单得到明细:
|
|||
|
|
select item_id,qty,name from lxm_sheet_item
|
|||
|
|
where sales_sheet_id = '845c7b9b-d1b0-47ff-8591-01a57b9f13c0'
|
|||
|
|
|
|||
|
|
5) 得到套餐的商品组合:
|
|||
|
|
select s.package_name,r.id,sku.name from lxm_relation r
|
|||
|
|
inner join
|
|||
|
|
lxm_product_sales_schedule s on r.main_id = s.id
|
|||
|
|
inner join
|
|||
|
|
lxm_sku sku on r.sub_id = sku.barcode_id
|
|||
|
|
where main_id = 2311171116200520
|
|||
|
|
|
|||
|
|
### 20240729
|
|||
|
|
1) 增加表api_log,api_name
|
|||
|
|
2) web.config修改配置
|