MemberService.php 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041
  1. <?php
  2. // +----------------------------------------------------------------------
  3. // | Laravel框架 [ Laravel ]
  4. // +----------------------------------------------------------------------
  5. // | 版权所有 2017~2021 Laravel研发中心
  6. // +----------------------------------------------------------------------
  7. // | 官方网站: http://www.laravel.cn
  8. // +----------------------------------------------------------------------
  9. // | Author: wesmiler <12345678@qq.com>
  10. // +----------------------------------------------------------------------
  11. namespace App\Services;
  12. use App\Models\CityModel;
  13. use App\Models\FansModel;
  14. use App\Models\FollowModel;
  15. use App\Models\MasterModel;
  16. use App\Models\MemberModel;
  17. use App\Models\SiyuanModel;
  18. use App\Models\TradeModel;
  19. use SimpleSoftwareIO\QrCode\Facades\QrCode;
  20. /**
  21. * 会员管理-服务类
  22. * @author wesmiler
  23. * @since 2020/11/11
  24. * Class MemberService
  25. * @package App\Services
  26. */
  27. class MemberService extends BaseService
  28. {
  29. protected static $instance = null;
  30. /**
  31. * 构造函数
  32. * @author wesmiler
  33. * @since 2020/11/11
  34. * MemberService constructor.
  35. */
  36. public function __construct()
  37. {
  38. $this->model = new MemberModel();
  39. $this->cityModel = new CityModel();
  40. }
  41. /**
  42. * 静态入口
  43. * @return MemberService|null
  44. */
  45. public static function make(){
  46. if(!self::$instance){
  47. self::$instance = new MemberService();
  48. }
  49. return self::$instance;
  50. }
  51. /**
  52. * 添加会编辑会员
  53. * @return array
  54. * @since 2020/11/11
  55. * @author wesmiler
  56. */
  57. public function edit()
  58. {
  59. // 请求参数
  60. $data = request()->all();
  61. // 头像处理
  62. $avatar = trim($data['avatar']);
  63. if (strpos($avatar, "temp")) {
  64. $data['avatar'] = save_image($avatar, 'member');
  65. } else {
  66. $data['avatar'] = str_replace(IMG_URL, "", $data['avatar']);
  67. }
  68. // 出生日期
  69. if (isset($data['birthday']) && $data['birthday']) {
  70. $data['birthday'] = strtotime($data['birthday']);
  71. }
  72. // 城市处理
  73. $city = isset($data['city']) ? $data['city'] : [3];
  74. if (!empty($data['city'])) {
  75. // 省份
  76. $data['province_id'] = $city[0];
  77. // 城市
  78. $data['city_id'] = $city[1];
  79. // 县区
  80. $data['district_id'] = $city[2];
  81. }
  82. unset($data['city']);
  83. return parent::edit($data); // TODO: Change the autogenerated stub
  84. }
  85. /**
  86. * 获取用户信息
  87. * @param $openid OPENID
  88. * @param array $field m用户表
  89. * @return array
  90. */
  91. public function getUserInfo($where, $field = [], $type=0)
  92. {
  93. $field = $field ? $field : ['m.id', 'm.openid', 'm.invite_id','m.birthday','m.age','m.gender','m.sign_time','m.intro','m.province_id','m.city_id','m.district_id', 'ms.type as mtype', 'yg.status as yigong_status','yg.on_siyuan as yg_on_siyuan','yg.siyuan_id','ms.siyuan','ms.on_siyuan as sr_on_siyuan', 'ms.master_type', 'ms.realname as master_name', 'ms.status as master_status', 'm.mobile', 'm.nickname', 'm.avatar', 'm.gender', 'm.member_level', 'm.is_vip','m.vip_expire', 'm.realname', 'm.balance', 'm.salary', 'm.merits_num', 'm.coupon', 'm.score', 'm.login_time','m.intro', 'm.status'];
  94. $info = $this->model::from('member as m')
  95. ->leftJoin('member_level as ml', 'ml.id', '=', 'm.member_level')
  96. ->leftJoin('master as ms', 'ms.user_id', '=', 'm.id')
  97. ->leftJoin('yigong as yg', 'yg.user_id', '=', 'm.id')
  98. ->select($field)
  99. ->where($where)
  100. ->where('m.status', '>', 0)
  101. ->first();
  102. $info = $info ? $info->toArray() : [];
  103. if (isset($info['avatar'])) {
  104. $info['avatar'] = $info['avatar'] ? get_image_url($info['avatar']) : '';
  105. }
  106. if($info){
  107. // 僧人参数
  108. if(array_key_exists('mtype', $info)){
  109. $info['mtype'] = $info['mtype']? intval($info['mtype']) : 0;
  110. }
  111. if(array_key_exists('master_type', $info)){
  112. $info['master_type'] = $info['master_type']? intval($info['master_type']) : 0;
  113. }
  114. if(array_key_exists('master_status', $info)){
  115. $info['master_status'] = $info['master_status']? intval($info['master_status']) : 0;
  116. }
  117. if(array_key_exists('master_name', $info)){
  118. $info['master_name'] = $info['master_name']? trim($info['master_name']) : '';
  119. }
  120. if(array_key_exists('yigong_siyuan', $info)){
  121. $info['yigong_siyuan'] = $info['yigong_siyuan']? trim($info['yigong_siyuan']) : '';
  122. }
  123. if(array_key_exists('yg_on_siyuan', $info)){
  124. $info['yg_on_siyuan'] = $info['yg_on_siyuan']? intval($info['yg_on_siyuan']) : 0;
  125. }
  126. if(array_key_exists('yigong_status', $info)){
  127. $info['yigong_status'] = $info['yigong_status']? intval($info['yigong_status']) : 0;
  128. }
  129. if(array_key_exists('siyuan', $info)){
  130. $info['siyuan'] = $info['siyuan']? trim($info['siyuan']) : '';
  131. }
  132. if(array_key_exists('sr_on_siyuan', $info)){
  133. $info['sr_on_siyuan'] = $info['sr_on_siyuan']? intval($info['sr_on_siyuan']) : 0;
  134. }
  135. $info['yg_siyuan'] = '';
  136. if(array_key_exists('siyuan_id', $info) && $info['siyuan_id']){
  137. $info['yg_siyuan'] = $info['siyuan_id']? SiyuanModel::where('id', $info['siyuan_id'])->value('title') : '';
  138. }
  139. if($type == 2){
  140. $master = MasterModel::where(['user_id'=> $info['id'],'mark'=> 1])
  141. ->first();
  142. if($master){
  143. $master['idcard_front_preview'] = $master['idcard_front']? get_image_url($master['idcard_front']) : '';
  144. $master['idcard_back_preview'] = $master['idcard_back']? get_image_url($master['idcard_back']) : '';
  145. $master['description_text'] = $master['description']? str_replace("\n","<br>", $master['description']) : '';
  146. }
  147. $info['master'] = $master? $master : [];
  148. }
  149. if($type == 3){
  150. if(isset($info['sign_time'])){
  151. $info['is_sign'] = $info['sign_time']>strtotime(date('Y-m-d'))? 1 : 0;
  152. $gd = ConfigService::make()->getConfigByCode('sign_give_gd');
  153. $info['sign_gd'] = $gd? $gd : 0;
  154. }
  155. $info['rank'] = 0;
  156. if($info['merits_num']>0){
  157. $model = $this->model::from(\DB::raw(env('DB_PREFIX').'member as m,(select (@rank:=0)) as rank'))
  158. ->where(['mark'=> 1,'status'=> 1])
  159. ->where('merits_num','>',0)
  160. ->orderBy('merits_num', 'desc')
  161. ->orderBy('create_time', 'asc')
  162. ->select(['merits_num','mark','id',\DB::raw('(@rank:=@rank+1) as rank')]);
  163. $binds = $model->getBindings();
  164. $sql = str_replace('?', '%s', $model->toSql());
  165. $sql = sprintf($sql, ...$binds);
  166. $ranks = $this->model::from(\DB::raw("({$sql}) as a"))
  167. ->where(['id'=> $info['id']])
  168. ->select(['merits_num','id','rank'])
  169. ->first();
  170. $info['rank'] = isset($ranks['rank'])? $ranks['rank'] : 0;
  171. }
  172. }
  173. if($type == 1) {
  174. // 城市
  175. $cityData = [0, 0, 0];
  176. $cityNames = [];
  177. if (isset($info['province_id'])) {
  178. $info['province_name'] = $info['province_id'] ? CityService::make()->getName($info['province_id']) : '';
  179. $cityData[0] = $info['province_id'] ? $info['province_id'] : 0;
  180. $cityNames[] = $info['province_name'] ? $info['province_name'] : '';
  181. }
  182. if (isset($info['city_id'])) {
  183. $info['city_name'] = $info['city_id'] ? CityService::make()->getName($info['city_id']) : '';
  184. $openid = isset($info['openid']) ? $info['openid'] : '';
  185. $cityData[1] = $info['city_id'] ? $info['city_id'] : 0;
  186. $cityNames[] = $info['city_name'] ? $info['city_name'] : '';
  187. if (empty($info['city_name']) && $openid) {
  188. $info['city_name'] = FansModel::where(['openid' => $openid])->value('city');
  189. }
  190. }
  191. if (isset($info['district_id'])) {
  192. $info['district_name'] = $info['district_id'] ? CityService::make()->getName($info['district_id']) : '';
  193. $cityData[2] = $info['district_id'] ? $info['district_id'] : 0;
  194. $cityNames[] = $info['district_name'] ? $info['district_name'] : '';
  195. }
  196. if ($cityData) {
  197. $info['cityData'] = $cityData;
  198. $info['cityText'] = implode(' ', array_filter($cityNames));
  199. }
  200. if (is_empty($info['age'])) {
  201. if (!is_empty($info['birthday'])) {
  202. $year = date('Y', $info['birthday']);
  203. $info['age'] = date('Y') - $year > 0 ? date('Y') - $year : 0;
  204. } else {
  205. $info['age'] = 0;
  206. }
  207. }
  208. if (is_empty($info['birthday'])) {
  209. $info['birthday'] = 0;
  210. } else {
  211. $info['birthday'] = date('Y-m-d', $info['birthday']);
  212. }
  213. if (is_empty($info['intro'])) {
  214. $info['intro'] = '';
  215. }
  216. if (is_empty($info['realname'])) {
  217. $info['realname'] = '';
  218. }
  219. // 二维码
  220. $url = env('WEB_URL') . '/pages/entry/auth?sid=' . $info['id'];
  221. $qrcode = WechatService::makeNormalQrcode($url);
  222. $info['qrcode'] = $qrcode ? get_image_url($qrcode) : '';
  223. // 会员
  224. if($info['is_vip'] && $info['vip_expire'] >= time()){
  225. $info['vip_expire_text'] = date('Y-m-d H:i:s', $info['vip_expire']);
  226. }else{
  227. $info['is_vip'] = 0;
  228. $info['vip_expire_text'] = '';
  229. }
  230. }
  231. }
  232. return $info;
  233. }
  234. /**
  235. * 统计表数据
  236. * @param $dateType
  237. * @return array
  238. */
  239. public function tableData($dateType){
  240. $cacheKey = "caches:statistics:table_member_{$dateType}";
  241. $datas = RedisService::get($cacheKey);
  242. if($datas){
  243. return $datas;
  244. }
  245. $firstTime = strtotime(date('Y-m-d'));
  246. if($dateType == 0){
  247. $formatStr = "%Y-%m-%d %H";
  248. $firstTime = strtotime(date('Y-m-d'));
  249. for($i = 0; $i<24; $i++){
  250. $i = $i<10? '0'.$i : $i;
  251. $ks = date('Y-m-d')." {$i}:00";
  252. $datas[$ks] = [
  253. 'date'=> $i.'点',
  254. 'ks'=> $ks,
  255. 'value'=> 0,
  256. ];
  257. }
  258. }else if($dateType == 1){
  259. $formatStr = "%Y-%m-%d";
  260. $firstTime = strtotime('monday this week');
  261. $weeks = ['周一','周二','周三','周四','周五','周六','周日'];
  262. for($i = 0; $i<7; $i++){
  263. $ks = date('Y-m-d', $firstTime+$i*86400);
  264. $datas[$ks] = [
  265. 'date'=> $weeks[$i],
  266. 'ks'=> $ks,
  267. 'value'=> 0,
  268. ];
  269. }
  270. }else if($dateType == 2){
  271. $formatStr = "%Y-%m-%d";
  272. $firstTime = strtotime(date('Y-m-01'));
  273. $num = cal_days_in_month(CAL_GREGORIAN, date('m'), date('Y'));
  274. for($i = 1; $i<=$num; $i++){
  275. $i = $i<10? '0'.$i : $i;
  276. $ks = date('Y-m')."-{$i}";
  277. $datas[$ks] = [
  278. 'date'=> $i.'号',
  279. 'ks'=> $ks,
  280. 'value'=> 0,
  281. ];
  282. }
  283. }else if($dateType == 3){
  284. $formatStr = "%Y-%m";
  285. $firstTime = strtotime(date('Y-01-01'));
  286. for($i = 1; $i<=12; $i++){
  287. $ks = date('Y')."-".($i<10? '0'.$i : $i);
  288. $datas[$ks] = [
  289. 'date'=> $i.'月',
  290. 'ks'=> $ks,
  291. 'value'=> 0,
  292. ];
  293. }
  294. }
  295. $counts = $this->model::from('member as a')
  296. ->where(['a.mark'=> 1,'a.status'=> 1])
  297. ->where('a.create_time','>=', $firstTime)
  298. ->where('a.create_time','<=', time())
  299. ->select([\DB::raw('count('.env('DB_PREFIX').'a.`id`) as value'),\DB::raw('FROM_UNIXTIME('.env('DB_PREFIX')."a.create_time,'{$formatStr}') as ks")])
  300. ->groupBy(\DB::raw('FROM_UNIXTIME('.env('DB_PREFIX')."a.create_time,'{$formatStr}')"))
  301. ->get();
  302. $counts = $counts? $counts->toArray() : [];
  303. if($counts){
  304. foreach ($counts as $v){
  305. if(isset($datas[$v['ks']])){
  306. $datas[$v['ks']]['value'] = $v['value'];
  307. }
  308. }
  309. }
  310. $datas = array_values($datas);
  311. RedisService::set($cacheKey, $datas, rand(5, 10));
  312. return $datas;
  313. }
  314. /**
  315. * 保存资料
  316. * @param $userId
  317. * @return array
  318. */
  319. public function saveInfo($userId){
  320. $params = request()->all();
  321. $memberInfo = $this->model::where(['id'=> $userId])->first();
  322. if(!$memberInfo){
  323. return message('用户账号不可操作', false);
  324. }
  325. if($params['avatar']){
  326. $memberInfo->avatar = trim($params['avatar']);
  327. }
  328. if($params['birthday']){
  329. $memberInfo->birthday = strtotime($params['birthday']);
  330. }
  331. if($params['age']){
  332. $memberInfo->age = strtotime($params['age']);
  333. }
  334. if(!is_empty($params['intro'])){
  335. $memberInfo->intro = trim($params['intro']);
  336. }
  337. if(!is_empty($params['mobile'])){
  338. $memberInfo->mobile = trim($params['mobile']);
  339. }
  340. if(!empty($params['cityCodes'])){
  341. $cityData = $params['cityCodes'];
  342. if(!is_empty($cityData[0]) && $cityData[0]){
  343. $memberInfo->province_id = $cityData[0];
  344. }
  345. if(!is_empty($cityData[1]) && $cityData[1]){
  346. $memberInfo->city_id = $cityData[1];
  347. }
  348. if(!is_empty($cityData[2]) && $cityData[2]){
  349. $memberInfo->district_id = $cityData[2];
  350. }
  351. }
  352. $memberInfo->nickname = trim($params['nickname']);
  353. $memberInfo->gender = intval($params['gender']);
  354. if($memberInfo->save()){
  355. return message('保存成功', true);
  356. }else{
  357. return message('保存失败', false);
  358. }
  359. }
  360. /**
  361. * 账号注销
  362. * @param $userId
  363. * @return array
  364. */
  365. public function logout($userId){
  366. $memberInfo = $this->model::where(['id'=> $userId,'mark'=> 1,'status'=> 1])->first();
  367. if(!$memberInfo){
  368. return message('用户账号不可操作', false);
  369. }
  370. $memberInfo->mark = 0;
  371. $memberInfo->status = 2;
  372. $memberInfo->black_remark = request()->get('remark','');
  373. if($memberInfo->save()){
  374. return message('操作成功', true);
  375. }else{
  376. return message('操作失败', false);
  377. }
  378. }
  379. /**
  380. * 邀请奖励
  381. * @param $userId 注册用户
  382. * @param $inviteId 邀请用户
  383. * @param string $nickname 注册用户昵称
  384. * @return bool
  385. */
  386. public function inviteAward($userId, $inviteId, $nickname=''){
  387. // 验证当前用户是否已经奖励过
  388. $cacheKey = "caches:invite:u{$userId}_s{$inviteId}";
  389. $check = TradeModel::where(['user_id'=> $userId,'type'=> 6,'source_uid'=> $inviteId,'status'=>1,'mark'=> 1])->value('id');
  390. if($check){
  391. RedisService::set($cacheKey.':error_catched', ['error'=>'当前奖励已发放','date'=> date('Y-m-d H:i:s')], 7200);
  392. return false;
  393. }
  394. // 验证当前邀请用户是否账户有效
  395. $inviteInfo = $this->model::where(['id'=> $inviteId,'mark'=> 1,'status'=> 1])
  396. ->select(['id','openid','nickname','score','coupon','merits_num'])
  397. ->first();
  398. if(!$inviteInfo){
  399. RedisService::set($cacheKey.':error_invite', ['error'=>'当前邀请用户状态异常','date'=> date('Y-m-d H:i:s')], 7200);
  400. return false;
  401. }
  402. // 是否到达人数限制
  403. $curTime = strtotime(date('Y-m-d'));
  404. $inviteConfig = ConfigService::make()->getConfigByGroup(14);
  405. $inviteLimit = isset($inviteConfig['invite_limit'])? $inviteConfig['invite_limit']['value'] : 0;
  406. $checkCount = TradeModel::where(['user_id'=> $userId,'type'=> 6,'status'=>1,'mark'=> 1])
  407. ->where('create_time','>=',$curTime)
  408. ->count('id');
  409. if($inviteLimit>0 && $checkCount>=$inviteLimit){
  410. RedisService::set($cacheKey.':error_limit', ['error'=>'今日奖励已到达限制人数:'.$inviteLimit,'date'=> date('Y-m-d H:i:s')], 7200);
  411. return false;
  412. }
  413. // 奖励花灯券
  414. \DB::beginTransaction();
  415. $coupon = isset($inviteConfig['invite_give_coupon'])? intval($inviteConfig['invite_give_coupon']['value']) : 0;
  416. if($coupon>0){
  417. // 账户
  418. if(!$this->model::where(['id'=> $inviteId])->increment('coupon', $coupon)){
  419. \DB::rollBack();
  420. RedisService::set($cacheKey.':error_coupon', ['error'=>'奖励花灯券账户更新失败:'.$coupon,'date'=> date('Y-m-d H:i:s')], 7200);
  421. return false;
  422. }
  423. $data = [
  424. 'user_id'=> $inviteId,
  425. 'source_uid'=> $userId,
  426. 'type'=> 6,
  427. 'coin_type'=> 1,
  428. 'pay_type'=> 4,
  429. 'money'=> $coupon,
  430. 'change_type'=> 1,
  431. 'balance'=> $inviteInfo->coupon,
  432. 'create_time'=> time(),
  433. 'remark'=> "邀请用户[{$nickname}],奖励{$coupon}花灯券",
  434. 'status'=> 1,
  435. ];
  436. if(!TradeModel::insertGetId($data)){
  437. \DB::rollBack();
  438. RedisService::set($cacheKey.':error_acccount_coupon', ['error'=>'奖励花灯券账户明细处理失败','data'=> $data,'date'=> date('Y-m-d H:i:s')], 7200);
  439. return false;
  440. }
  441. }
  442. // 奖励功德值
  443. $giveGd = isset($inviteConfig['invite_give_gd'])? intval($inviteConfig['invite_give_gd']['value']) : 0;
  444. if($giveGd>0){
  445. // 账户
  446. if(!$this->model::where(['id'=> $inviteId])->increment('merits_num', $giveGd)){
  447. \DB::rollBack();
  448. RedisService::set($cacheKey.':error_gd', ['error'=>'奖励功德值账户更新失败:'.$giveGd,'date'=> date('Y-m-d H:i:s')], 7200);
  449. return false;
  450. }
  451. $data = [
  452. 'user_id'=> $inviteId,
  453. 'source_uid'=> $userId,
  454. 'type'=> 6,
  455. 'coin_type'=> 4,
  456. 'pay_type'=> 4,
  457. 'money'=> $giveGd,
  458. 'change_type'=> 1,
  459. 'balance'=> $inviteInfo->merits_num,
  460. 'create_time'=> time(),
  461. 'remark'=> "邀请用户[{$nickname}],奖励{$giveGd}功德值",
  462. 'status'=> 1,
  463. ];
  464. if(!TradeModel::insertGetId($data)){
  465. \DB::rollBack();
  466. RedisService::set($cacheKey.':error_acccount_gd', ['error'=>'奖励花灯券账户明细处理失败','data'=> $data,'date'=> date('Y-m-d H:i:s')], 7200);
  467. return false;
  468. }
  469. }
  470. // 奖励积分
  471. \DB::commit();
  472. return true;
  473. }
  474. /**
  475. * 加入会员
  476. * @param $userId
  477. */
  478. public function buyVip($userId){
  479. $memberInfo = $this->model::where(['id'=> $userId,'mark'=> 1,'status'=> 1])
  480. ->select(['id','nickname','openid','coupon','mobile','is_vip','vip_expire'])
  481. ->first();
  482. if(!$memberInfo){
  483. return message('用户账户不可操作,请联系客服',false);
  484. }
  485. $params = request()->all();
  486. $receiveVipMessage = isset($params['receive_vip_message'])? $params['receive_vip_message'] : 0;
  487. if($receiveVipMessage>0 && empty($memberInfo->mobile)){
  488. return message('接收消息通知,请先到个人资料设置手机号码再尝试',false);
  489. }
  490. // 是否已经加入了会员
  491. $isVip = false;
  492. if($memberInfo->is_vip && $memberInfo->vip_expire>=time()){
  493. $isVip = true;
  494. }
  495. // 验证账户
  496. $config = ConfigService::make()->getConfigByGroup(15);
  497. $vipPrice = isset($config['vip_price'])? intval($config['vip_price']['value']) : 0;
  498. $vipGiveCoupon = isset($config['vip_give_coupon'])? intval($config['vip_give_coupon']['value']) : 0;
  499. if($vipPrice<=0){
  500. return message('当前会员价格参数设置错误,请联系客服',false);
  501. }
  502. if($vipPrice<=$vipGiveCoupon){
  503. return message('当前会员赠送参数设置错误,请联系客服',false);
  504. }
  505. if($memberInfo->coupon < $vipPrice){
  506. return message('您的账户花灯券不足,请先充值后重试',false,[],'10003');
  507. }
  508. // 操作账户
  509. \DB::beginTransaction();
  510. if(!$this->model::where(['id'=> $userId,'mark'=> 1])->decrement('coupon',($vipPrice - $vipGiveCoupon))){
  511. \DB::rollBack();
  512. return message('扣除用户账户失败,请刷新后重试',false);
  513. }
  514. // 更新会员有效期
  515. if($isVip){
  516. $vipExpire = $memberInfo->vip_expire+365*24*3600;
  517. }else{
  518. $vipExpire = time()+365*24*3600;
  519. }
  520. if(!$this->model::where(['id'=> $userId,'mark'=> 1])->update(['is_vip'=> 1,'receive_vip_message'=> $receiveVipMessage,'vip_expire'=> $vipExpire])){
  521. \DB::rollBack();
  522. return message('更新会员有效期失败,请刷新后重试',false);
  523. }
  524. // 账户明细
  525. $data = [
  526. 'user_id'=> $userId,
  527. 'type'=> 1,
  528. 'coin_type'=> 1,
  529. 'pay_type'=> 1,
  530. 'money'=> $vipPrice,
  531. 'change_type'=> 2,
  532. 'balance'=> $memberInfo->coupon,
  533. 'create_time'=> time(),
  534. 'remark'=> ($isVip? '续费':'加入')."会员扣除{$vipPrice}花灯券",
  535. 'status'=> 1,
  536. ];
  537. if(!TradeModel::insertGetId($data)){
  538. \DB::rollBack();
  539. return message('处理账户明细失败,请刷新后重试',false);
  540. }
  541. // 赠送
  542. if($vipGiveCoupon>0){
  543. $data = [
  544. 'user_id'=> $userId,
  545. 'type'=> 3,
  546. 'coin_type'=> 1,
  547. 'pay_type'=> 4,
  548. 'money'=> $vipGiveCoupon,
  549. 'change_type'=> 1,
  550. 'balance'=> ($memberInfo->coupon-$vipPrice),
  551. 'create_time'=> time()+1,
  552. 'remark'=> ($isVip? '续费':'加入')."会员赠送{$vipGiveCoupon}花灯券",
  553. 'status'=> 1,
  554. ];
  555. if(!TradeModel::insertGetId($data)){
  556. \DB::rollBack();
  557. return message('赠送券处理失败,请刷新后重试',false);
  558. }
  559. }
  560. \DB::commit();
  561. return message(($isVip? '续费':'加入').'会员成功',true);
  562. }
  563. /**
  564. * 签到
  565. * @param $userId
  566. * @return array
  567. */
  568. public function sign($userId){
  569. $memberInfo = $this->model::where(['id'=> $userId, 'mark'=>1 ,'status'=> 1])
  570. ->select(['id','openid','nickname','score','merits_num','sign_time'])
  571. ->first();
  572. if(!$memberInfo){
  573. return message('账户不可操作,请联系客服',false);
  574. }
  575. if($memberInfo->sign_time >= strtotime(date('Y-m-d'))){
  576. return message('您今天已签到过,请明天再来',false);
  577. }
  578. // 参数
  579. $score = ConfigService::make()->getConfigByCode('sign_give_score');
  580. $score = $score? $score : 0;
  581. // 处理
  582. \DB::beginTransaction();
  583. if($score>0){
  584. if(!$this->model::where(['id'=> $userId,'mark'=> 1])->increment('score', $score)){
  585. \DB::rollBack();
  586. return message('更新账户积分失败,请刷新后重试',false);
  587. }
  588. $data = [
  589. 'user_id'=> $userId,
  590. 'type'=> 3,
  591. 'coin_type'=> 3,
  592. 'pay_type'=> 4,
  593. 'money'=> $score,
  594. 'change_type'=> 1,
  595. 'balance'=> $memberInfo->score,
  596. 'create_time'=> time(),
  597. 'remark'=> "每日签到赠送{$score}积分",
  598. 'status'=> 1,
  599. ];
  600. if(!TradeModel::insertGetId($data)){
  601. \DB::rollBack();
  602. return message('积分奖励处理失败,请刷新后重试',false);
  603. }
  604. }
  605. $giveGd = ConfigService::make()->getConfigByCode('sign_give_gd');
  606. $giveGd = $giveGd? $giveGd : 0;
  607. if($giveGd>0){
  608. if(!$this->model::where(['id'=> $userId,'mark'=> 1])->increment('merits_num', $giveGd)){
  609. \DB::rollBack();
  610. return message('更新账户功德失败,请刷新后重试',false);
  611. }
  612. $data = [
  613. 'user_id'=> $userId,
  614. 'type'=> 3,
  615. 'coin_type'=> 4,
  616. 'pay_type'=> 4,
  617. 'money'=> $giveGd,
  618. 'change_type'=> 1,
  619. 'balance'=> $memberInfo->merits_num,
  620. 'create_time'=> time(),
  621. 'remark'=> "每日签到赠送{$giveGd}功德",
  622. 'status'=> 1,
  623. ];
  624. if(!TradeModel::insertGetId($data)){
  625. \DB::rollBack();
  626. return message('签到功德奖励处理失败,请刷新后重试',false);
  627. }
  628. }
  629. if(!$this->model::where(['id'=> $userId,'mark'=> 1])->update(['sign_time'=> time()])){
  630. \DB::rollBack();
  631. return message('签到处理失败,请刷新后重试',false);
  632. }
  633. \DB::commit();
  634. return message('签到成功',true);
  635. }
  636. /**
  637. * 获取功德榜列表
  638. * @return array
  639. * @since 2020/11/11
  640. * @author wesmiler
  641. */
  642. public function gdList($userId)
  643. {
  644. $params = request()->all();
  645. $page = isset($params['pageSize']) ? intval($params['pageSize']) : PAGE;
  646. $pageSize = isset($params['pageSize']) ? intval($params['pageSize']) : PERPAGE;
  647. $dataList = $this->model::where(['mark'=>1,'status'=> 1])
  648. ->where('merits_num','>',0)
  649. ->select(['id', 'avatar', 'nickname','gender','merits_num','status','create_time'])
  650. ->orderBy('merits_num', 'desc')
  651. ->orderBy('create_time', 'asc')
  652. ->paginate($pageSize);
  653. $dataList = $dataList ? $dataList->toArray() : [];
  654. if ($dataList) {
  655. foreach ($dataList['data'] as &$item) {
  656. $item['avatar'] = $item['avatar']? get_image_url($item['avatar']) : '';
  657. $item['create_time'] = $item['create_time'] ? datetime($item['create_time'],'Y-m-d H:i:s') : '';
  658. }
  659. unset($item);
  660. }
  661. $meritsNum = $this->model::where(['id'=> $userId])->value('merits_num');
  662. $ranks['id'] = $userId;
  663. $ranks['rank'] = 0;
  664. $ranks['merits_num'] = $meritsNum>0? $meritsNum : 0;
  665. if($meritsNum>0){
  666. $model = $this->model::from(\DB::raw(env('DB_PREFIX').'member as m,(select (@rank:=0)) as rank'))
  667. ->where(['mark'=> 1,'status'=> 1])
  668. ->where('merits_num','>',0)
  669. ->orderBy('merits_num', 'desc')
  670. ->orderBy('create_time', 'asc')
  671. ->select(['merits_num','mark','id',\DB::raw('(@rank:=@rank+1) as rank')]);
  672. $binds = $model->getBindings();
  673. $sql = str_replace('?', '%s', $model->toSql());
  674. $sql = sprintf($sql, ...$binds);
  675. $ranks = $this->model::from(\DB::raw("({$sql}) as a"))
  676. ->where(['id'=> $userId])
  677. ->select(['merits_num','id','rank'])
  678. ->first();
  679. }
  680. return [
  681. 'code' => 0,
  682. 'success'=> true,
  683. 'msg' => '操作成功',
  684. 'ranks' => $ranks,
  685. 'count' => isset($dataList['total']) ? $dataList['total'] : 0,
  686. 'data' => isset($dataList['data']) ? $dataList['data'] : 0,
  687. ];
  688. }
  689. /**
  690. * 关注
  691. * @param $userId
  692. * @return array
  693. */
  694. public function follow($userId){
  695. $params = request()->all();
  696. $followUid = isset($params['follow_uid'])? $params['follow_uid'] : 0;
  697. $status = isset($params['status'])? $params['status'] : 1;
  698. $status = $status<=0? 1 : $status;
  699. if(empty($followUid)){
  700. return message('参数错误', false);
  701. }
  702. if(!in_array($status, [1,2])){
  703. return message('参数错误', false);
  704. }
  705. $memberInfo = $this->model::where(['id'=> $userId, 'mark'=> 1,'status'=> 1])
  706. ->select(['id','openid','nickname','status'])
  707. ->first();
  708. if(!$memberInfo){
  709. return message('账户不可操作已冻结或不存在,请联系客服',false);
  710. }
  711. $followInfo = $this->model::where(['id'=> $followUid, 'mark'=> 1,'status'=> 1])
  712. ->select(['id','openid','nickname','status'])
  713. ->first();
  714. if(!$followInfo){
  715. return message('被关注用户不可操作或不存在',false);
  716. }
  717. $info = FollowModel::where(['user_id'=> $userId, 'follow_uid'=> $followUid])->select(['id','status'])->first();
  718. if($info && $info->status == 1 && $status == 1){
  719. return message('您已关注过', false);
  720. }else if($info && $info->status == 2 && $status == 2){
  721. return message('您已取消关注', false);
  722. }else if(!$info && $status == 2){
  723. return message('您未关注过', false);
  724. }
  725. // 处理
  726. if($info){
  727. $info->status = $status;
  728. $info->create_time = time();
  729. if($info->save()){
  730. return message($status == 1? '关注成功':'取消关注成功', true);
  731. }
  732. }else{
  733. $data = [
  734. 'user_id'=> $userId,
  735. 'follow_uid'=> $followUid,
  736. 'create_time'=> time(),
  737. 'status'=> 1,
  738. ];
  739. if(FollowModel::insertGetId($data)){
  740. return message('关注成功', true);
  741. }
  742. }
  743. return message('操作失败', false);
  744. }
  745. /**
  746. * 获取选项列表
  747. * @param int $num
  748. * @return array
  749. */
  750. public function getOptions(){
  751. $params = request()->all();
  752. $num = isset($params['num'])? $params['num'] : 0;
  753. $type = isset($params['type'])? $params['type'] : 0;
  754. $where = ['status'=>1,'mark'=> 1];
  755. if($type>0){
  756. $where['type'] = $type;
  757. }
  758. $datas = $this->model->where($where)
  759. ->select(['id','nickname','status'])
  760. ->limit($num? $num : 999999)
  761. ->get();
  762. return message(MESSAGE_OK, true, $datas);
  763. }
  764. /**
  765. * 关注的人
  766. * @return array
  767. * @since 2020/11/11
  768. * @author wesmiler
  769. */
  770. public function followList($userId)
  771. {
  772. $params = request()->all();
  773. $page = isset($params['pageSize']) ? intval($params['pageSize']) : PAGE;
  774. $pageSize = isset($params['pageSize']) ? intval($params['pageSize']) : PERPAGE;
  775. $dataList = $this->model::from('member_follow as mf')
  776. ->leftJoin('member as m','mf.follow_uid','=','m.id')
  777. ->where(['m.mark'=>1,'m.status'=> 1,'mf.mark'=> 1,'mf.status'=> 1])
  778. ->select(['mf.id', 'mf.user_id','mf.follow_uid','m.avatar','m.nickname', 'm.merits_num','m.gender','m.status','m.create_time'])
  779. ->orderBy('m.merits_num', 'desc')
  780. ->orderBy('mf.create_time', 'asc')
  781. ->paginate($pageSize);
  782. $dataList = $dataList ? $dataList->toArray() : [];
  783. if ($dataList) {
  784. foreach ($dataList['data'] as &$item) {
  785. $item['avatar'] = $item['avatar']? get_image_url($item['avatar']) : '';
  786. $item['create_time'] = $item['create_time'] ? datetime($item['create_time'],'Y-m-d H:i:s') : '';
  787. }
  788. unset($item);
  789. }
  790. return [
  791. 'code' => 0,
  792. 'success'=> true,
  793. 'msg' => '操作成功',
  794. 'count' => isset($dataList['total']) ? $dataList['total'] : 0,
  795. 'data' => isset($dataList['data']) ? $dataList['data'] : 0,
  796. ];
  797. }
  798. /**
  799. * 感兴趣的人
  800. * @param $userId
  801. * @return array
  802. */
  803. public function recommand($userId){
  804. $params = request()->all();
  805. $page = isset($params['pageSize']) ? intval($params['pageSize']) : PAGE;
  806. $pageSize = isset($params['pageSize']) ? intval($params['pageSize']) : PERPAGE;
  807. $type = isset($params['type'])? $params['type'] : 0;
  808. $where = ['m.mark'=>1,'m.status'=> 1];
  809. $dataList = $this->model::from('member as m')
  810. ->leftJoin('master as ms','ms.user_id','=','m.id')
  811. ->leftJoin('member_follow as mf','mf.follow_uid','=','m.id')
  812. ->where($where)
  813. ->where(function($query) use($type){
  814. if($type==1){
  815. $query->where('ms.type',1)->where(['ms.mark'=> 1,'ms.status'=> 1]);
  816. }else if($type == 2){
  817. $query->whereNotIn('ms.type',[1])->where(['ms.mark'=> 1,'ms.status'=> 1]);
  818. }
  819. })
  820. ->whereNotIn('m.id', [$userId])
  821. ->whereNotIn('m.id',function($query) use ($userId){
  822. $query->from('member_follow')->where(['user_id'=> $userId])->select(['follow_uid']);
  823. })
  824. ->select(['m.id', 'm.avatar','m.nickname','ms.realname as master_name','ms.type as mtype','m.gender','m.status','m.create_time'])
  825. ->orderBy('mf.create_time', 'asc')
  826. ->paginate($pageSize);
  827. $dataList = $dataList ? $dataList->toArray() : [];
  828. if ($dataList) {
  829. foreach ($dataList['data'] as &$item) {
  830. $item['avatar'] = $item['avatar']? get_image_url($item['avatar']) : '';
  831. $item['create_time'] = $item['create_time'] ? datetime($item['create_time'],'Y-m-d H:i:s') : '';
  832. }
  833. unset($item);
  834. }
  835. return [
  836. 'code' => 0,
  837. 'success'=> true,
  838. 'msg' => '操作成功',
  839. 'count' => isset($dataList['total']) ? $dataList['total'] : 0,
  840. 'data' => isset($dataList['data']) ? $dataList['data'] : 0,
  841. ];
  842. }
  843. /**
  844. * VIP 用户每月奖励
  845. * @return array
  846. */
  847. public function vipAward(){
  848. $cacheKey = "caches:vip:awards:logs:".date('YmdHi');
  849. $num = ConfigService::make()->getConfigByCode('vip_award_catch_num');
  850. $num = $num>10? $num : 200;
  851. // 有效会员
  852. $users = $this->model::where(['mark'=>1,'status'=>1])
  853. ->where('vip_expire','>=', time())
  854. ->where('give_time','<', strtotime(date('Y-m-01')))
  855. ->select(['id','nickname','coupon','give_time','vip_expire'])
  856. ->limit($num)
  857. ->get();
  858. $giveNum = ConfigService::make()->getConfigByCode('vip_month_give_coupon');
  859. $giveNum = $giveNum? $giveNum : 0;
  860. RedisService::set($cacheKey.':data', ['users'=> $users,'give'=>$giveNum], 86400);
  861. if($users && $giveNum>0){
  862. $success = [];
  863. $fail = [];
  864. foreach ($users as $v){
  865. // 处理用户
  866. $result = $this->vipAwardCatch($v,$giveNum);
  867. if($result['success'] == true){
  868. $success[] = [
  869. 'id'=>$v['id'],
  870. 'result'=> $result,
  871. ];
  872. }else{
  873. $fail[] = [
  874. 'id'=>$v['id'],
  875. 'result'=> $result,
  876. ];
  877. }
  878. }
  879. }
  880. if($success || $fail){
  881. $msg = 'VIP每月奖励处理完成:成功'.count($success).'个,失败'.count($fail).'个';
  882. RedisService::set($cacheKey.':result',['success'=> $success,'fail'=> $fail,'result'=> $msg], 7200);
  883. return message($msg, true, ['success'=> $success,'fail'=> $fail]);
  884. }else{
  885. return message('VIP每月奖励处理失败或暂无用户需处理', false);
  886. }
  887. }
  888. /**
  889. * VIP奖励处理
  890. * @param $userInfo 用户信息,至少包含 id、coupon字段
  891. * @param $giveNum
  892. * @return array
  893. */
  894. public function vipAwardCatch($userInfo,$giveNum){
  895. $userId = $userInfo->id;
  896. $cacheKey = "caches:vip:awards:users:u_{$userId}";
  897. \DB::beginTransaction();
  898. // 扣除账户
  899. if(!MemberModel::where(['id'=> $userId,'mark'=> 1])->increment('coupon', $giveNum)){
  900. \DB::rollBack();
  901. RedisService::set($cacheKey.':error', ['info'=>$userInfo,'give'=> $giveNum, 'error'=> '更新账户失败'], 7200);
  902. return message('更新会员券账户失败',false);
  903. }
  904. // 明细
  905. $data = [
  906. 'user_id'=> $userId,
  907. 'type'=> 3,
  908. 'coin_type'=> 1,
  909. 'pay_type'=> 1,
  910. 'money'=> $giveNum,
  911. 'change_type'=> 1,
  912. 'balance'=> $userInfo->coupon,
  913. 'create_time'=> time(),
  914. 'update_time'=> time(),
  915. 'remark'=> 'VIP会员每月赠送奖励',
  916. 'status'=> 1
  917. ];
  918. if(!TradeModel::insertGetId($data)){
  919. \DB::rollBack();
  920. RedisService::set($cacheKey.':error', ['info'=>$userInfo,'data'=> $data,'give'=> $giveNum, 'error'=> '交易明细处理失败'], 7200);
  921. return message( "交易处理失败,请刷新重试", false);
  922. }
  923. // 更新处理时间
  924. $this->model::where(['id'=> $userId,'mark'=>1])->update(['give_time'=> time()]);
  925. \DB::commit();
  926. return message( "VIP用户[ID:{$userId}]每月奖励{$giveNum}个花灯券处理成功", true);
  927. }
  928. }